Automatically convert a number to use the correct SI unit prefix

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


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


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

Tags:

Number

Siunitx