5

Say one is talking about numbers 10^6, 10^7, 10^8, 10^9 and so forth one can also write them as 1M, 10M, 100M,1G, with their corresponding SI unit prefixes.

Is there a way to convert a number on the form

 10000000...
 1E6 
 1E-6
-100000... 

To a number with SI unit prefixes automatically?

------------------------------------
  Input              |      Output
------------------------------------
 \prefix{1000000}    |         1M
 \prefix{10000000}   |        10M
 \prefix{100000000}  |       100M
 \prefix{1000000000} |         1G
 \prefix{1E6}        |         1M
 \prefix{-1E6}       |         1μ
------------------------------------
  • Can you give a sample of what you hope the usage syntax should look like? – Steven B. Segletes Jun 24 at 16:42
  • Done. I added an example – N3buchadnezzar Jun 24 at 16:48
  • Thanks. I was just wondering if it has to interact with siunitx calls in a prescribed way, or is what you are showing in your example sufficient? – Steven B. Segletes Jun 24 at 16:53
  • 1
    No interaction needed. – N3buchadnezzar Jun 24 at 16:54
  • 1
    @PhelypeOleinik My understanding is that the application is specifically for powers of 10 only, though the OP can correct me. – Steven B. Segletes Jun 24 at 18:50
4

This allows any (valid, of course) value or expression to be input. The code (ab)uses the LaTeX3 floating point unit to parse the input expression and convert it to exponential notation. With that the code just looks for the correct prefix to apply to that number. Numbers outside the range are printed with more digits to make the number fit the nearest prefix (i.e., 1E28 prints 10000 Y and 1E-28 prints 0.0001 y).

The \prefix macro (which should probably be called \postfix :-) is expandable, takes one argument, the number, and prints the it followed by the prefix.

The \prefixSI macro is not expandable and has the same signature as siunitx's \SI macro. \prefixSI evaluates its first mandatory argument with \prefix (\nebu_prefix:n, actually), as described above, and passes it to \SI. The selected prefix is stored in \prefix (inside a group, so that the macro \prefix can be used agian) which can be used in an unit expression, for example \prefixSI{1000}{\prefix\metre} prints 1 km.

Prefixes can be declared with \setprefix \<siunitx prefix macro> { <prefix> } { <value> }, like \setprefix \centi { c } { -2 }. Make sure that the \<siunitx prefix macro> exists. If you're not going to use \prefixSI then you can use \relax there.

And, as the code uses expl3's floating point unit to parse the number, you are not limited to the aEb or decimal notation. You can do any floating point operation implemented in l3fp (look here for a list of them), like a*10^b or any other.

\documentclass{article}
\usepackage{siunitx}
\ExplSyntaxOn
% Internals
\int_new:N \l__nebu_min_prefix_int
\tl_new:N \l__nebu_base_number_tl
\tl_new:N \l__nebu_mode_tl
\cs_new:Npn \nebu_prefix:n #1
  {
    \exp_args:Nf \__nebu_prefix:n
      { \exp_args:Nf \fp_to_scientific:n { \tl_lower_case:n {#1} } }
  }
\cs_new:Npn \__nebu_prefix:n #1
  { \__nebu_prefix:nwnw #1 \q_stop }
\cs_new:Npn \__nebu_prefix:nwnw #1 e #2 \q_stop
  { \__nebu_find_prefix:nnn {#2} {#1} {1} }
\cs_new:Npn \__nebu_find_prefix:nnn #1 #2 #3
  {
    \tl_if_exist:cT { l__nebu_ #1 \tl_use:N \l__nebu_mode_tl _prefix_tl }
      {
        \use_i_delimit_by_q_stop:nw
          { \__nebu_output:nnn {#2} {#1} {#3} }
      }
    \int_compare:nNnT {#1} < \l__nebu_min_prefix_int
      {
        \use_i_delimit_by_q_stop:nw
          {
            \exp_args:Nf \__nebu_find_prefix:nnn
              { \int_eval:n { \l__nebu_min_prefix_int } } {#2}
              { #3 / 1\prg_replicate:nn { \l__nebu_min_prefix_int - #1 }{ 0 } }
          }
      }
    \use_i:nn
      {
        \exp_args:Nf \__nebu_find_prefix:nnn
          { \int_eval:n {#1-1} } {#2} {#3*10}
      }
      \q_stop
  }
\exp_args_generate:n { fv }
\cs_new:Npn \__nebu_output:nnn #1 #2 #3
  {
    \exp_args:Nfv \nebu_output:nn
      { \fp_to_decimal:n {#1*#3} } { l__nebu_ #2 \tl_use:N \l__nebu_mode_tl _prefix_tl }
  }
\cs_new_protected:Npn \nebu_prefix_set:Nnn #1 #2 #3
  {
    \exp_args:Nxx \__nebu_prefix_set:nnN
      { \tl_trim_spaces:n {#2} } { \int_eval:n {#3} } #1
  }
\cs_new_protected:Npn \__nebu_prefix_set:nnN #1 #2 #3
  {
    \tl_clear_new:c { l__nebu_ #2 _prefix_tl }
    \tl_clear_new:c { l__nebu_ #2 _siunitx_prefix_tl }
    \tl_set:cn { l__nebu_ #2 _prefix_tl } {#1}
    \tl_set:cn { l__nebu_ #2 _siunitx_prefix_tl } {#3}
    \int_set:Nn \l__nebu_min_prefix_int { \int_min:nn {#2} { \l__nebu_min_prefix_int } }
  }
% User interface
\NewExpandableDocumentCommand \prefix { m }
  { \nebu_prefix:n {#1} }
\cs_new:Npn \nebu_output:nn #1 #2 { #1\, \textrm{#2} }
\NewDocumentCommand \setprefix { m m m }
  { \nebu_prefix_set:Nnn #1 {#2} {#3} }
\DeclareSIPrefix \none { } { 0 }
\setprefix \none { } { 0 }
%
\cs_new_protected:Npn \__nebu_store:nn #1 #2
  {
    \tl_set:Nn \l__nebu_base_number_tl {#1}
    \cs_set:Npn \prefix {#2}
  }
\NewExpandableDocumentCommand \prefixSI { o m o m }
  {
    \group_begin:
      \cs_set_eq:NN \nebu_output:nn \__nebu_store:nn
      \tl_set:Nn \l__nebu_mode_tl { _siunitx }
      \nebu_prefix:n {#2}
      \SI [#1] { \l__nebu_base_number_tl } [#3] {#4}
    \group_end:
  }
\ExplSyntaxOff

\setprefix \yocto { y } { -24 }
\setprefix \zepto { z } { -21 }
\setprefix \atto  { a } { -18 }
\setprefix \femto { f } { -15 }
\setprefix \pico  { p } { -12 }
\setprefix \nano  { n } { -9 }
\setprefix \micro { \SIUnitSymbolMicro } { -6 }
\setprefix \milli { m } { -3 }
\setprefix \centi { c } { -2 }
\setprefix \deci  { d } { -1 }
\setprefix \deca  { da } { 1 }
\setprefix \hecto { h }  { 2 }
\setprefix \kilo  { k }  { 3 }
\setprefix \mega  { M }  { 6 }
\setprefix \giga  { G }  { 9 }
\setprefix \tera  { T }  { 12 }
\setprefix \peta  { P }  { 15 }
\setprefix \exa   { E }  { 18 }
\setprefix \zetta { Z }  { 21 }
\setprefix \yotta { Y }  { 24 }

\begin{document}
\prefix{1}\par
\prefix{4E6}\par
\prefix{1E-15}\par
\prefix{1e-6}\par
\prefix{300}\par
\prefix{1000000}\par
\prefix{.001}\par
\prefix{.000004}\par
\prefix{1E5}\par
\prefix{10000}\par
\prefix{.0001}\par
\prefix{5E7}\par
\prefix{1E-5}\par
\prefix{3E8}\par
\prefix{1E-4}\par
\prefix{1E-24}\par
\prefix{1E-28}\par
\prefix{1E24}\par
\prefix{1E28}\par
\prefix{1e3*pi}\par
\prefixSI{1e3*pi}{\prefix\metre}\par
\end{document}

enter image description here

  • How would 10E-4 metres look? ;-) Great answer! – N3buchadnezzar Jun 24 at 21:22
  • 2
    @N3buchadnezzar Thanks! A millimetre: 1 mm? Is there something wrong? – Phelype Oleinik Jun 24 at 21:27
  • Well in most countries 10^-4 metres is known as 0.1 mm, but I understand why it displays it as 1 mm. Just something to be aware of when using this answer – N3buchadnezzar Jun 28 at 12:58
  • @N3buchadnezzar 10e-4 m is 10×10^-4 m, which is 10×0.1mm, which is 1mm. The e in 10e-4 is short for ×10^, so the leading 10 multiplies the whole thing, thus the difference you see. – Phelype Oleinik Jun 28 at 13:05
4

EDITED to solve both input forms. EDITED so that either capital or lowercase E is permitted in the scientific notation (e.g., 1E6 or 1e6).

EDITED to automatically discern multipliers of 1, 10 or 100 on standard units.

If the prefix is outside the range of 1E-24 to 1E26, a ?? is output.

See SUPPLEMENT for more general use of multipliers (outside of 1, 10, 100).

As this answer has expanded with EDITS, let me explan the logic. There are 3 macros that call each other in sequence. Let me work my way from the end to the beginning. The lowest level macro is

\prefixSN[<multiplier>]{1E<SI-prefix-integer>}

The multiplier defaults to 1, but can be 10 or 100. The SI-prefix-integer are specific integers for which an SI prefix is defined (-24, -21, ...-6, -3, -2, -1, 0, 1, 2, 3, 6, ...21, 24). If the provided integer is not in this list, a ?? is output. If the integer is in the list, the appropriate SI-prefix is output following the multiplier.

The macro that feeds \prefixSN in the proper syntax is called \prefixmult, with syntax

\prefixmult{1E<integer>}

Here integer should be in the range -24 to 26. Based on the value of the integer, it sets the prefix as 1, 10, or 100, and adjusts the integer to the next lower SI standard value. For example if the input to \prefixmult was 1E5, it would set the call \prefixSI[100]{1E3}.

The top-level macro, \prefix, which feeds \prefixmult its integer format, can take input in four possible form s:

\prefix{1E<integer>}
\prefix{1e<integer>}
\prefix{10<...>0}
\prefix{.0<...>01}

It sorts through looking for an E or e. Finding one, it passes the capital-E form to \prefixmult with the associated integer. If no E is found, it looks for a decimal point, a 1 and multiple 0 tokens. Based on what it finds, it reconstructs the associated integer based on the number of zeros and passes it onto \prefixmult.

Here is the MWE.

\documentclass{article}
\usepackage{siunitx,listofitems}
\newcommand\prefixSN[2][1]{#1\,\prefixSNaux#2\relax}
\def\prefixSNaux 1E#1\relax{%
  \ifnum#1=0\relax\else
  \ifnum#1=-1\relax\textrm{d}\else  
  \ifnum#1=-2\relax\textrm{c}\else  
  \ifnum#1=-3\relax\textrm{m}\else  
  \ifnum#1=-6\relax\textrm{\ensuremath{\mu}}\else  
  \ifnum#1=-9\relax\textrm{n}\else  
  \ifnum#1=-12\relax\textrm{p}\else  
  \ifnum#1=-15\relax\textrm{f}\else  
  \ifnum#1=-18\relax\textrm{a}\else  
  \ifnum#1=-21\relax\textrm{z}\else  
  \ifnum#1=-24\relax\textrm{y}\else  
  \ifnum#1=1\relax\textrm{da}\else  
  \ifnum#1=2\relax\textrm{h}\else  
  \ifnum#1=3\relax\textrm{k}\else  
  \ifnum#1=6\relax\textrm{M}\else  
  \ifnum#1=9\relax\textrm{G}\else  
  \ifnum#1=12\relax\textrm{T}\else  
  \ifnum#1=15\relax\textrm{P}\else  
  \ifnum#1=18\relax\textrm{E}\else  
  \ifnum#1=21\relax\textrm{Z}\else  
  \ifnum#1=24\relax\textrm{Y}\else 
  ??%
  \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
}
\newcommand\prefix[1]{%
  \setsepchar{1E||1e}%
  \readlist\scinot{#1}%
  \ifnum\scinotlen>1\relax
    \itemtomacro\scinot[2]\temp%
    \expandafter\prefixmult\expandafter{\expandafter1\expandafter E\temp}%
  \else
    \setsepchar{./1/0}%
    \readlist\zerocount{#1}%
    \ifnum\zerocountlen=1\relax
      \expandafter\prefixmult\expandafter{\expandafter1\expandafter E%
        \the\numexpr\listlen\zerocount[1,2]-1\relax}%
    \else
      \expandafter\prefixmult\expandafter{\expandafter1\expandafter E%
        \expandafter-\the\numexpr\listlen\zerocount[2,1]\relax}%
    \fi
  \fi
}
\newcommand\prefixmult[1]{\prefixmultaux#1\relax}
\def\prefixmultaux 1E#1\relax{%
  \setsepchar{E4||E7||E10||E13||E16||E19||E22||E25||%
              E-5||E-8||E-11||E-14||E-17||E-20||E-23}%
  \readlist\factormult{E#1}%
  \ifnum\factormultlen>1\relax
    \prefixSN[10]{1E\the\numexpr#1-1\relax}
  \else
    \setsepchar{E5||E8||E11||E14||E17||E20||E23||E26||%
                E-4||E-7||E-10||E-13||E-16||E-19||E-22}%
    \readlist\factormult{E#1}%
    \ifnum\factormultlen>1\relax
      \prefixSN[100]{1E\the\numexpr#1-2\relax}
    \else
      \prefixSN[1]{1E#1}%
    \fi
  \fi
}
\begin{document}
\prefix{1E6}\par
\prefix{1E-15}\par
\prefix{1e-6}\par
\prefix{100}\par
\prefix{1000000}\par
\prefix{.001}\par
\prefix{.000001}\par
\prefix{1E5}\par
\prefix{10000}\par
\prefix{.0001}\par
\prefix{1E7}\par
\prefix{1E-5}\par
\prefix{1E8}\par
\prefix{1E-4}
\end{document}

enter image description here

SUPPLEMENT

Not sure if this is an improvement, as it seems to me that the \prefix should deal solely in powers of 10. However, if one wished for a more general implementation that could handle any multiplier of a single significant digit, then here it is:

\documentclass{article}
\usepackage{siunitx,listofitems}
\newcommand\prefixSN[2][1]{#1\,\prefixSNaux#2\relax}
\def\prefixSNaux 1E#1\relax{%
  \ifnum#1=0\relax\else
  \ifnum#1=-1\relax\textrm{d}\else  
  \ifnum#1=-2\relax\textrm{c}\else  
  \ifnum#1=-3\relax\textrm{m}\else  
  \ifnum#1=-6\relax\textrm{\ensuremath{\mu}}\else  
  \ifnum#1=-9\relax\textrm{n}\else  
  \ifnum#1=-12\relax\textrm{p}\else  
  \ifnum#1=-15\relax\textrm{f}\else  
  \ifnum#1=-18\relax\textrm{a}\else  
  \ifnum#1=-21\relax\textrm{z}\else  
  \ifnum#1=-24\relax\textrm{y}\else  
  \ifnum#1=1\relax\textrm{da}\else  
  \ifnum#1=2\relax\textrm{h}\else  
  \ifnum#1=3\relax\textrm{k}\else  
  \ifnum#1=6\relax\textrm{M}\else  
  \ifnum#1=9\relax\textrm{G}\else  
  \ifnum#1=12\relax\textrm{T}\else  
  \ifnum#1=15\relax\textrm{P}\else  
  \ifnum#1=18\relax\textrm{E}\else  
  \ifnum#1=21\relax\textrm{Z}\else  
  \ifnum#1=24\relax\textrm{Y}\else 
  ??%
  \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi
}
\newcommand\prefix[1]{%
  \setsepchar{E||e}%
  \readlist\scinot{#1}%
  \ifnum\scinotlen>1\relax
    \itemtomacro\scinot[1]\premult%
    \itemtomacro\scinot[2]\temp%
    \expandafter\expandafter\expandafter\prefixmult
    \expandafter\expandafter\expandafter{%
    \expandafter\premult\expandafter E\temp}%
  \else
    \setsepchar{./1||2||3||4||5||6||7||8||9/0}%
    \readlist\zerocount{#1}%
    \ifnum\zerocountlen=1\relax
      \expandafter\expandafter\expandafter\def
        \expandafter\expandafter\expandafter\temp
        \expandafter\expandafter\expandafter{\zerocountsep[1,1]}%
      \expandafter\expandafter\expandafter\prefixmult
        \expandafter\expandafter\expandafter{%
        \expandafter\temp\expandafter E%
        \the\numexpr\listlen\zerocount[1,2]-1\relax}%
    \else
      \expandafter\expandafter\expandafter\def
        \expandafter\expandafter\expandafter\temp
        \expandafter\expandafter\expandafter{\zerocountsep[2,1]}%
      \expandafter\expandafter\expandafter\prefixmult
        \expandafter\expandafter\expandafter{%
        \expandafter\temp\expandafter E%
        \expandafter-\the\numexpr\listlen\zerocount[2,1]\relax}%
    \fi
  \fi
}
\newcommand\prefixmult[1]{\prefixmultaux#1\relax}
\def\prefixmultaux #1E#2\relax{%
  \setsepchar{E4||E7||E10||E13||E16||E19||E22||E25||%
              E-5||E-8||E-11||E-14||E-17||E-20||E-23}%
  \readlist\factormult{E#2}%
  \ifnum\factormultlen>1\relax
    \prefixSN[#10]{1E\the\numexpr#2-1\relax}
  \else
    \setsepchar{E5||E8||E11||E14||E17||E20||E23||E26||%
                E-4||E-7||E-10||E-13||E-16||E-19||E-22}%
    \readlist\factormult{E#2}%
    \ifnum\factormultlen>1\relax
      \prefixSN[#100]{1E\the\numexpr#2-2\relax}
    \else
      \prefixSN[#1]{1E#2}%
    \fi
  \fi
}
\begin{document}
\prefix{4E6}\par
\prefix{1E-15}\par
\prefix{1e-6}\par
\prefix{300}\par
\prefix{1000000}\par
\prefix{.001}\par
\prefix{.000004}\par
\prefix{1E5}\par
\prefix{10000}\par
\prefix{.0001}\par
\prefix{5E7}\par
\prefix{1E-5}\par
\prefix{3E8}\par
\prefix{1E-4}
\end{document}

enter image description here

  • Awesome! Now all that remains is for someone to figure out how to convert \prefix{10000} to 10k =) – N3buchadnezzar Jun 24 at 17:35
  • 1
    @N3buchadnezzar Ask and ye shall receive. Done. – Steven B. Segletes Jun 24 at 18:17
4

This supports both exponential and standard notation.

No check about the nonzero digit is performed, the input is assumed to be of the form

1<sequence of zeros>
-1<sequence of zeros>
1e<exponent>
1E<exponent>
-1e<exponent>
-1E<exponent>

The range is -1E-26 to 1E26. Numbers outside this range silently fail. You can add control about the range.

Here's the code: note that the bulk of the work is performed by \__nebuch_prefix_exp:nn (precisely by the :ee variant).

\documentclass{article}
\usepackage{siunitx} % also loads xparse and expl3

\ExplSyntaxOn
\NewDocumentCommand{\prefix}{m}
 {
  \nebuch_prefix:e { \tl_lower_case:n { #1 } }
 }

\seq_new:N \l__nebuch_prefix_input_seq

\cs_new_protected:Nn \nebuch_prefix:n
 {
  \seq_set_split:Nnn \l__nebuch_prefix_input_seq { e } { #1 }
  \int_compare:nTF { \seq_count:N \l__nebuch_prefix_input_seq > 1 }
   {% there was e/E
    \__nebuch_prefix_exp:ee
     { \seq_item:Nn \l__nebuch_prefix_input_seq { 1 } }
     { \seq_item:Nn \l__nebuch_prefix_input_seq { 2 } }
   }
   {% no e/E
    \__nebuch_prefix_long:n { #1 }
   }
 }
\cs_generate_variant:Nn \nebuch_prefix:n { e }

\cs_new_protected:Nn \__nebuch_prefix_exp:nn
 {
  \int_compare:nTF { \int_mod:nn { #2 } { 3 } < 0 }
   {
    \__nebuch_prefix_exp:ee
     { #1 \prg_replicate:nn { 3 + \int_mod:nn { #2 } { 3 } } { 0 } }
     { \int_eval:n { #2 - 3 - \int_mod:nn { #2 } { 3 } } }
   }
   {
    \SI
     {
      #1 % 1 or -1
      \prg_replicate:nn { \int_mod:nn { #2 } { 3 } } { 0 }
     }
     {
      \int_case:nn { \int_div_truncate:nn { #2 } { 3 } }
       {
        {-8}{\yocto\voidunit}
        {-7}{\zepto\voidunit}
        {-6}{\atto\voidunit}
        {-5}{\femto\voidunit}
        {-4}{\pico\voidunit}
        {-3}{\nano\voidunit}
        {-2}{\micro\voidunit}
        {-1}{\milli\voidunit}
        {0}{\!\voidunit}
        {1}{\kilo\voidunit}
        {2}{\mega\voidunit}
        {3}{\giga\voidunit}
        {4}{\tera\voidunit}
        {5}{\peta\voidunit}
        {6}{\exa\voidunit}
        {7}{\zetta\voidunit}
        {8}{\yotta\voidunit}
       }
     }
   }
 }
\cs_generate_variant:Nn \__nebuch_prefix_exp:nn {ee}

\cs_new_protected:Nn \__nebuch_prefix_long:n
 {
  % count the number of zeros
  \regex_split:nnN { (\-*1+) } { #1 } \l__nebuch_prefix_input_seq
  \__nebuch_prefix_exp:ee
   { \seq_item:Nn \l__nebuch_prefix_input_seq { 2 } }
   { \tl_count:e { \seq_item:Nn \l__nebuch_prefix_input_seq { 3 } } }
 }
\cs_generate_variant:Nn \tl_count:n { e }
\ExplSyntaxOff

\DeclareSIUnit{\voidunit}{\relax}

\begin{document}

\prefix{1e0}

\prefix{1e2}

\prefix{1E4}

\prefix{-1E20}

\prefix{1}

\prefix{10}

\prefix{100}

\prefix{1000}

\prefix{10000}

\prefix{100000}

\prefix{1000000}

\prefix{10000000}

\prefix{100000000}

\prefix{1000000000}

\prefix{-10000000000}

\prefix{100000000000}

\prefix{1E6}

\prefix{1E24}

\prefix{-1E-6}

\prefix{1E-8}

\prefix{-1E-7}

\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.