6

I thought I was able to do something like this before, but I can't find my solution.

I would like to format my large numbers as follows:

 12,504.3124 2359 3

I know I can use siunitx to partially set this up, but I'm not getting the results I want:

\documentclass{article}
\usepackage{siunitx}

\newcommand\aenumA[1][4]{%%
  \num[group-separator={,},
       group-digits=integer,
       ]}

\newcommand\aenumB[1][4]{%%
  \num[group-digits=decimal,
       ]}

\pagestyle{empty}
\begin{document}

  \aenumA{12504.312423593}

  \aenumB{12504.312423593}

\end{document}

I would like a hybrid solution (commas in the integer part and spaces in the decimal). I would actually also like to be able to group differently between the two: every three digits in the integer part, every four digits in the decimal part.

Is there a straight-forward way to do this with siunitx? Or, do I need to split the number at the decimal and treat the two parts separately and then glue them back together?

3
  • 1
    Sorry, but this is a straight 'no' even as a feature request. The whole point of the package is to provide tools for consistency: this is anything but.
    – Joseph Wright
    May 19, 2014 at 21:14
  • I fully agree with @JosephWright. Why separating groups of four in the decimal part, by the way?
    – egreg
    May 19, 2014 at 21:16
  • @egreg Generally, I agree. Groups of four vs. groups of three is definitely bad form. But, it's the form I'm being asked for. But on the other hand, forgetting inconsistency in the grouping size, I'm sure I've seen commas used in the integer part and spaces for the decimal. That alone doesn't seem too bad.
    – A.Ellett
    May 19, 2014 at 21:19

3 Answers 3

3

A completely expandable solution (expands in two steps, and expands its number argument).

This update uses \, for inserting a space between the group of four digits (thus the spaces are visible also in math mode). The macro handles successfully fringe cases such as \aenum {} or \aenum {.}. It detects a leading + or - sign. It may be used as \aenum{\A.\B} if \A and \B expand to a string of digits, or \aenum{\C} if \C expands to an integer or decimal number.

The code can be modified to insert whatever one wants. To allow the user to specify the separator, I would first recycle the code here to rather group the digits in successive braced groups, and then I would use the \xintListWithSep utility from xinttools (another possible approach is to insert some macros as separators, i.e. \sepINT, \sepDEC but this is less clean than having the explicit tokens one wants without needing further expansion) . It could even be possible to have the user-specified separators be optional arguments (within brackets) and maintain expandability, but this would add a layer of complication.

the more general approach as sketched in the first half of the above paragraph has been implemented in the code sample at the bottom of this answer

splitting numbers

\documentclass{article}
\usepackage{xinttools}% for token manipulation utilities

\makeatletter

% THIS IS A COMPLETELY EXPANDABLE MACRO
% 

% We use _ as private letter.
% We use \@gobble, \@firstoftwo, \@secondoftwo
% although xinttools has
% \xint_gobble_i    = \@gobble
% \xint_firstoftwo  = \@firstoftwo
% \xint_secondoftwo = \@secondoftwo

\catcode`_ 11
\makeatletter

% The argument is (left) expanded, it may be a macro. 
%   (even input such as \A.\B is allowed)
%    
% A - or + sign is correctly identified.
% It may contain or not contain a decimal mark `.'
% If the number has a `.' both the integer and decimal parts
% are allowed to be empty.

% \aenum{.} and  \aenum {} are both allowed.

\newcommand{\aenum}[1]
  {\romannumeral0\expandafter\aenum@aa\romannumeral-`0#1..\relax}

% helper macro (there are similar ones in xint)

\def\xint_minusplusfork #1-+#2#3\krof {#2}                            

% to deal with numbers starting either with +, with -, or without a sign.
\def\aenum@aa #1{\xint_minusplusfork 
                   #1+{\expandafter-\romannumeral0\aenum@a}%
                   -#1{\expandafter+\romannumeral0\aenum@a}%
                    -+{\aenum@a #1}%
                 \krof }

% We need to fork according to whether there is a decimal mark or none.
% decimal mark present: #3=. (and #2 is authorized to be empty)
% decimal mark absent:  #3=empty
\def\aenum@a #1.#2.#3\relax
{\if\relax #3\relax\expandafter\@firstoftwo
             \else\expandafter\@secondoftwo
 \fi
 {\aenum@comma@three@l {#1}}%
 {\expandafter\aenum@b\expandafter
             {\romannumeral0\aenum@comma@three@l {#1}}%
             {\romannumeral0\aenum@space@four@r {#2}}}%
}
\def\aenum@b #1#2{\expandafter\aenum@c\expandafter{#2}{#1}}
\def\aenum@c #1#2{ #2.#1}


%% GROUPING BY THREE FROM THE RIGHT TOWARDS THE LEFT
%% AND INSERTING , AS SEPARATOR

\def\aenum@comma@three@l #1%
   {\expandafter\xintreverseorder\expandafter
    {\romannumeral0\aenum@comma@three@r{\xintReverseOrder{#1}}}}

\def\aenum@comma@three@r #1{\expandafter\aenum@comma@three@ra
                            \romannumeral-`0#1\relax\relax\relax\dummy }

% we here check here if "short", for technical reasons
\def\aenum@comma@three@ra #1#2#3{\ifx #3\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {\aenum@comma@three@rshort #1#2#3}{\aenum@comma@three@rb {#1#2#3}}}

% initial space to feed the \romannumeral0. The #1 may be empty.
\def\aenum@comma@three@rshort #1\relax #2\dummy { #1}

\def\aenum@comma@three@rb #1#2#3#4{\ifx #4\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {\aenum@comma@three@rc {#1}#2#3#4}{\aenum@comma@three@rb {#1,#2#3#4}}}

\def\aenum@comma@three@rc #1#2\relax #3\dummy {\if\relax #2\relax
    \expandafter\@firstoftwo 
    \else\expandafter\@secondoftwo
    \fi % Notice that #1 is never empty in this branch
    { #1}{ #1,#2}}% initial space to feed the \romannumeral0 

%% GROUPING BY FOUR GOING FROM THE LEFT TO THE RIGHT
%% We also expand the decimal part which thus could have been so far a
%% macro.
% THIS VERSION USES '\,' AS SEPARATOR.  

\def\aenum@space@four@r #1{\expandafter\aenum@space@four@ra
                           \romannumeral-`0#1\relax\relax\relax\relax\dummy }

% we first check here if "short", for technical reasons
\def\aenum@space@four@ra #1#2#3#4{\ifx #4\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {\aenum@space@four@rshort #1#2#3#4}{\aenum@space@four@rb {#1#2#3#4}}}

% initial space to feed the \romannumeral0. The #1 may be empty.
\def\aenum@space@four@rshort #1\relax #2\dummy { #1}

\def\aenum@space@four@rb #1#2#3#4#5{\ifx #5\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {\aenum@space@four@rc {#1}#2#3#4#5}{\aenum@space@four@rb {#1\,#2#3#4#5}}}

\def\aenum@space@four@rc #1#2\relax #3\dummy {\if\relax #2\relax
    \expandafter\@firstoftwo 
    \else\expandafter\@secondoftwo
    \fi % Notice that #1 is never empty in this branch
    { #1}{ #1\,#2}}% initial space to feed the \romannumeral0 

\makeatother
\catcode`_ 8

\begin{document}

  Fringe case: 
  X\aenum{}Y\aenum{+}Z\aenum{-}T\aenum{.}Y\aenum{+.}Z\aenum{-.}T


  \aenum{1.2}\quad \aenum{1.23}\quad \aenum{1.234}\quad \aenum{1.2345}\quad \aenum{1.23456}\quad \aenum{1.234567}\quad \aenum{1.2345678}

  \aenum{-12.3}\quad \aenum{-123.3}\quad \aenum{-1234.3}\quad \aenum{-12345.3}\quad \aenum{-123456.3}\quad \aenum{-1234567.3} 

  Math mode:

  $\aenum{+12504.312423593}$

  $\aenum{-12504.312423593}$

  $\aenum{-1250412504.312423593312423593}$

  $\aenum{+12345678912345678912345.12345678123456781234567}$

\end{document}

More general approach. It defines \aegroupbythreeL and \aegroupbyfourR which expandably group tokens three by three from the right, respectively four by four from the left. It then uses \xintListWithSep from xinttools to insert arbitrarily specified separators.

Possibly such "grouping" macros will be added to a future release of xinttools.

ae num with separators

\documentclass{article}
\usepackage{xinttools}% for \xintLength macro only

\makeatletter
% We use _ as private letter.
% We use \@gobble, \@firstoftwo, \@secondoftwo for easing up reading the
% code to people not used to the xinttools names:
% \xint_gobble_i    = \@gobble
% \xint_firstoftwo  = \@firstoftwo
% \xint_secondoftwo = \@secondoftwo

\catcode`_ 11

% DEFINITION of a completely expandable macro (expands in two steps)
% \aenumwithsep with three arguments:

% The number argument is expanded, it may be a macro. 
%   (and even input such as \A.\B is allowed)
%    
% A - or + sign is correctly identified.
% It may contain or not contain a decimal mark `.'
% If the number has a `.' both the integer and decimal parts
% are allowed to be empty.

% THIS VARIANT \aenumwithsep HAS THREE ARGUMENTS:
% #1 = separator for integer part
% #2 = separator for decimal part
% #3 = actual number, may be empty or just a dot. May start with an
% optional + or - sign.

\newcommand {\aenumwithsep}[3]
 {\romannumeral0\expandafter\aenumwithsep@aa\romannumeral-`0#3..\relax
  {#1}{#2}}

% helper macro (there are similar ones in xint)

\def\xint_minusplusfork #1-+#2#3\krof {#2}                            

% to deal with numbers starting either with +, with -, or without a sign.
\def\aenumwithsep@aa #1{\xint_minusplusfork 
                   #1+{\expandafter-\romannumeral0\aenumwithsep@a}%
                   -#1{\expandafter+\romannumeral0\aenumwithsep@a}%
                    -+{\aenumwithsep@a #1}%
                 \krof }

% We need to fork according to whether there is a decimal mark or none.
% decimal mark present: #3=. (and #2 is authorized to be empty)
% decimal mark absent:  #3=empty
\def\aenumwithsep@a #1.#2.#3\relax #4#5%
{\if\relax #3\relax\expandafter\@firstoftwo
             \else\expandafter\@secondoftwo
 \fi
 {\xintlistwithsep {#4}{\aegroupbythreeL {#1}}}%
 {\expandafter\aenumwithsep@b\expandafter
             {\romannumeral0\xintlistwithsep{#4}{\aegroupbythreeL {#1}}}%
             {\romannumeral0\xintlistwithsep{#5}{\aegroupbyfourR {#2}}}}%
}
\def\aenumwithsep@b #1#2{\expandafter\aenumwithsep@c\expandafter{#2}{#1}}
\def\aenumwithsep@c #1#2{ #2.#1}


%% GROUPING BY THREE FROM THE RIGHT TOWARDS THE LEFT
%% 1234567-->{1}{234}{567}

%% Uses `\xintLength` to get the length. First we expand the argument
%% although not needed here. But in case the macro is used elsewhere, we
%% define it as a macro expanding in two steps and expanding its
%% argument (from the left)

\def\aegroupbythreeL #1{\romannumeral0\expandafter\aegroupbythreeL@aa
                        \romannumeral-`0#1\relax }

\def\aegroupbythreeL@aa #1\relax
    {\expandafter\aegroupbythree@a\expandafter 
         {\romannumeral-`0\xintLength{#1}}#1\relax\relax\relax
    }

\def\aegroupbythree@a #1{\ifnum #1>\thr@@ 
                                  \expandafter\@firstoftwo
                            \else\expandafter\@secondoftwo
                        \fi
                        {\ifcase\numexpr #1-(#1/\thr@@)*\thr@@\relax
                         \expandafter\aegroupbythree@A\or
                         \expandafter\aegroupbythree@B\else
                         \expandafter\aegroupbythree@C\fi }%
                        {\ifnum #1>\z@
                          \expandafter\aegroupbythree@short
                         \else\expandafter\aegroupbythree@empty
                         \fi }}

\def\aegroupbythree@short #1\relax\relax\relax{{#1}}
\def\aegroupbythree@empty \relax\relax\relax{ }% Return empty list, and
                                % not list with one empty braced item


\def\aegroupbythree@A #1#2#3{\aegroupbythree@b {{#1#2#3}}}
\def\aegroupbythree@B #1{\aegroupbythree@b {{#1}}}
\def\aegroupbythree@C #1#2{\aegroupbythree@b {{#1#2}}}

\def\aegroupbythree@b #1#2#3#4{\ifx #4\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {#1}{\aegroupbythree@b {#1{#2#3#4}}}}

%% GROUPING BY FOUR GOING FROM THE LEFT TO THE RIGHT
%% 1234567-->{1234}{567}

\def\aegroupbyfourR #1{\romannumeral0\expandafter\aegroupbyfourR@a
                        \romannumeral-`0#1\relax\relax\relax\relax\dummy }

% we first check here if "short", for technical reasons
\def\aegroupbyfourR@a #1#2#3#4{\ifx #4\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {\aegroupbyfourR@short #1#2#3#4}{\aegroupbyfourR@b {{#1#2#3#4}}}}

% space to feed the \romannumeral0. We need to check for emptiness
%  
\def\aegroupbyfourR@short #1\relax #2\dummy {\if\relax #1\relax
    \expandafter\@firstoftwo 
    \else\expandafter\@secondoftwo
    \fi { }{{#1}}}

\def\aegroupbyfourR@b #1#2#3#4#5{\ifx #5\relax
    \expandafter\@firstoftwo
    \else\expandafter\@secondoftwo
    \fi
    {\aegroupbyfourR@c {#1}#2#3#4#5}{\aegroupbyfourR@b {#1{#2#3#4#5}}}}

\def\aegroupbyfourR@c #1#2\relax #3\dummy {\if\relax #2\relax
    \expandafter\@firstoftwo 
    \else\expandafter\@secondoftwo
    \fi % Notice that #1 is never empty in this branch
    {#1}{#1{#2}}}

\makeatother
\catcode`_ 8

\newcommand{\aenum}[1]{\aenumwithsep {,}{\,}{#1}}

\begin{document}

  Fringe cases: 
  X\aenum{}Y\aenum{+}Z\aenum{-}T\aenum{.}Y\aenum{+.}Z\aenum{-.}T


  \aenum{1.2}\quad \aenum{1.23}\quad \aenum{1.234}\quad \aenum{1.2345}\quad \aenum{1.23456}\quad \aenum{1.234567}\quad \aenum{1.2345678}

  \aenum{-12.3}\quad \aenum{-123.3}\quad \aenum{-1234.3}\quad \aenum{-12345.3}\quad \aenum{-123456.3}\quad \aenum{-1234567.3} 

  Math mode:

  $\aenum{+12504.312423593}$

  $\aenum{-12504.312423593}$

  $\aenum{-1250412504.312423593312423593}$

  $\aenum{+12345678912345678912345.12345678123456781234567}$

  $\aenum {1234}\neq\aenum{12345}\neq\aenum{123456}\neq\aenum{1234567}$

 With separators $!$ and $?$:

 $\aenumwithsep !?{+1234567.1234567}$

 $\aenumwithsep !?{+12345678912345678912345.12345678123456781234567}$

 Testing expansion
\begin{verbatim}
\expandafter\expandafter\expandafter
  \def
\expandafter\expandafter\expandafter
  \x
\expandafter\expandafter\expandafter
  {\aenumwithsep \a\b{+12345678912345678912345.12345678123456781234567}}
\ttfamily\meaning\x
\end{verbatim}
\expandafter\expandafter\expandafter
  \def
\expandafter\expandafter\expandafter
  \x
\expandafter\expandafter\expandafter
  {\aenumwithsep \a\b{+12345678912345678912345.12345678123456781234567}}
\ttfamily\meaning\x

(spaces after \string\a\space and \string\b\space come from \string\meaning)

% \expandafter\expandafter\expandafter
%   \def
% \expandafter\expandafter\expandafter
%   \x
% \expandafter\expandafter\expandafter
%   {\aenumwithsep !?{.}}
% \ttfamily\meaning\x+++
\end{document}
1
  • The first method handles the grouping by three using \xintReverseOrder, the second method, for a change, does this using rather first evaluation of the length of the input via \xintLength. For a similar case using \xintLength see tex.stackexchange.com/a/145000/4686
    – user4686
    May 20, 2014 at 13:20
2

Using the code from https://tex.stackexchange.com/a/171012/4427

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\aenum}{ >{\SplitArgument{1}{.}} m }
 {
  \aenumaux#1
 }

\NewDocumentCommand{\aenumaux}{mm}
 {
  \ae_add_commas:n { #1 }
  \IfValueT{#2} % no decimal part
   { . \ae_add_spaces:n { #2 } }
 }

\tl_new:N \l__ae_number_temp_tl

\cs_new_protected:Npn \ae_add_commas:n #1
 {
  \tl_set:Nn \l__ae_number_temp_tl { #1 }
  % we need to start from the end, so we reverse the string
  \tl_reverse:N \l__ae_number_temp_tl
  % add a comma after any group of three tokens
  \regex_replace_all:nnN { (.{3}) } { \1\, } \l__ae_number_temp_tl
  % if the length of the string is a multiple of three a trailing comma is added
  % so we remove it
  \regex_replace_once:nnN { \,\Z } { } \l__ae_number_temp_tl
  % reverse back
  \tl_reverse:N \l__ae_number_temp_tl
  \tl_use:N \l__ae_number_temp_tl
 }

\cs_new_protected:Npn \ae_add_spaces:n #1
 {
  \tl_set:Nn \l__ae_number_temp_tl { #1 }
  \regex_replace_all:nnN { (.{4}) } { \1\cS\  } \l__ae_number_temp_tl
  \regex_replace_once:nnN { \cS.\Z } { } \l__ae_number_temp_tl
  \tl_use:N \l__ae_number_temp_tl
 }

\ExplSyntaxOff

\begin{document}

\aenum{12504}

\aenum{12504.312423593}

\aenum{0.12345678}

\end{document}

enter image description here

2

The following example makes the group-separator of package siunitx dependent on a switch \ifSiunitxDecimal, which indicates, whether the decimal part is reached. The switch is set to false. When the output-decimal-marker is used, the switch is set to true. The hack part comes, when the switch is reset again to false after the number is printed. This is done by escaping two group levels via \aftergroup.

\documentclass{article}
\usepackage{siunitx}

\newif\ifSiunitxDecimal
\protected\def\SiunitxDecimalfalse{\global\let\ifSiunitxDecimal\iffalse}
\protected\def\SiunitxDecimaltrue{\global\let\ifSiunitxDecimal\iftrue}
\sisetup{
  output-decimal-marker={.\SiunitxDecimaltrue
    \aftergroup\aftergroup\aftergroup\SiunitxDecimalfalse
  },
  group-separator={\ifSiunitxDecimal\,\else{,}\fi},
  group-minimum-digits=3,
  retain-explicit-plus,
}

\begin{document}
$\num{+12504.312423593}\quad\num{-12345.54321}$\quad
\num{123456789}\quad\num{.987654321}
\end{document}

Result

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .