8

I want to write a macro for removing the zero in the decimal. e.g. convert 2.0 to 2 as number.

this is my try for removing one zero but it doesn't work and produces error:

\documentclass[borders=2cm]{standalone}
\usepackage{tikz}
\newcommand{\isinteger}[1]{\pgfmathtruncatemacro{\intvar}{#1}
                           \def\newx{\pgfmathparse{10*(\intvar-#1)}\pgfmathresult}
    \ifnum\newx=0 
    \intvar
    \else 
    #1
    \fi}
\begin{document}
    \isinteger{1.5}
\end{document}

Errors:

Missing number, treated as zero. \isinteger{1.5}
Missing = inserted for \ifnum. \isinteger{1.5}
Missing number, treated as zero. \isinteger{1.5}

Any Idea?

2
  • Does the solution have to be tikz-based, or would a LuaLaTeX-based solution be acceptable as well?
    – Mico
    Commented Sep 18, 2020 at 13:56
  • it is better to be pdfLaTeX compilable..
    – user108724
    Commented Sep 18, 2020 at 14:05

5 Answers 5

14

If you just want to print an integer as an integer, and a non-integer as a non-integer, and since you are already using pgfmath, I recommend using \pgfmathprintnumber. It is described in detail in chapter 98 of the TikZ manual, but the following seems to do what you want:

\documentclass{article}
\usepackage{tikz}
\newcommand{\isinteger}[1]{\pgfmathprintnumber[int detect,fixed]{#1}}

\begin{document}
    \isinteger{1.5}
    \isinteger{1.0}
\end{document}

This prints 1.5 and 1, respectively.

Another option is to use \num from siunitx:

\documentclass{article}
\usepackage{siunitx}
\newcommand{\isinteger}[1]{\num[zero-decimal-to-integer=true]{#1}}

\begin{document}
    \isinteger{1.5}
    \isinteger{1.0}
\end{document}
4
  • I have used your solution to this post. forgive me.
    – user108724
    Commented Sep 18, 2020 at 15:23
  • @C.F.G Everything here is in the public domain and you have even added a reference, so it's all good!
    – user30471
    Commented Sep 18, 2020 at 21:50
  • How to do it with \num of siunitx?
    – Diaa
    Commented Sep 23, 2020 at 9:40
  • 1
    @Diaa See my edit
    – user30471
    Commented Sep 23, 2020 at 10:58
13

enter image description here

\documentclass{article}

\ExplSyntaxOn
\newcommand\isintegerTF[1]{
  \fp_compare:nNnTF
    {#1}={floor(#1)}
 }
\ExplSyntaxOff

\begin{document}

\isintegerTF{2}{2 yes}{2 no}


\isintegerTF{1.5}{1.5 yes}{1.5 no}

\end{document}
28
  • 4
    It can't be! Is that expl3 in your answer? ;-) Commented Sep 18, 2020 at 14:11
  • 1
    @MadyYuvi no I do not use xparse at all here Commented Sep 18, 2020 at 14:14
  • 3
    @PhelypeOleinik well as I have a gold pgf badge that would have been too easy to use Commented Sep 18, 2020 at 14:15
  • 4
    floor is the standard function to give the integer below the supplied floating point value expl3 includes a full IEEE floating point implementation and floor is one of the IEEE specified functions Commented Sep 18, 2020 at 14:20
  • 2
    @C.F.G that is the benefit of the expl3 nameing scheme you can see that it has effectively 5 arguments nNnTF that is the first expression , the comparison operator, the second expression, the true branch and the false branch. So if the 5 arguments were {1+2}<{5-1}{yes}{no} it would expand to yes Commented Sep 19, 2020 at 15:53
12

A variant of David's answer, if the aim is just to not print trailing zeros:

\documentclass{article}
\usepackage{xfp}

\begin{document}

\fpeval{1}

\fpeval{1.0}

\fpeval{1.5}

\fpeval{14/5-4/5}

\fpeval{round(4*pi*3.4^3/3,0)} % round to integer

\end{document}

enter image description here

Note that the fp module of expl3 (that's used here) is much more accurate than the PGF floating point utilities.

6

Just for the sake of variety, here's a LuaLaTeX-based implemenation of \IsInteger.

enter image description here

If the argument of \IsInteger doesn't evaluate to an integer, the argument itself is returned. E.g., if the argument is a string -- say, \IsInteger{"uvw"} -- you'll get back "uvw". Conversely, if the argument does evaluate to an integer, the integer representation of the number is returned. E.g., \IsInteger{1.0} is shown as 1, not as 1.0.

What exactly "evaluates to" means in practice is determined by the C math routines that are used by the version of Lua that's embedded in LuaTeX. (LuaTeX 1.12.0, which is part of TeXLive2020, employs Lua 5.3.5.) For maximum discriminatory power between integers and floating point numbers, it's best to check if a (very small) number is equal to 0. E.g., \IsInteger{1e-323} returns 9.8813129168249e-324 (a floating point number), whereas \IsInteger{1e-324} returns 0 (an integer). The discriminatory power is much lower for integer benchmarks other than 0. For instance, whereas \IsInteger{1+1e-15} returns 1.0 (a floating point number, not an integer), \IsInteger{1e-16} returns 1. Of course, in practical applications it may be perfectly alright to treat 1+1e-16 as being equal to 1...

% !TEX TS-program = lualatex
\documentclass{article}
\newcommand\IsInteger[1]{\directlua{% % make use of Lua's 'math.tointeger' function
    local y; y = math.tointeger ( #1 ); tex.sprint ( #1==y and y or #1 )}}

\begin{document}
\IsInteger{1.5}, \IsInteger{1.0}, \IsInteger{math.pi}, \IsInteger{math.exp(math.log(1))}
\end{document}
2

I can offer an expandable routine \normalizenumber for "normalizing" numbers.

In order to explain how \normalizenumber works let me define—in addition to what is defined of TeX's grammar in the TeXBook in Backus/Naur-notation— a quantity ⟨decimal separator⟩:

⟨decimal separator⟩.12 | ,12

Syntax of \normalizenumber is:

\normalizenumber⟨undelimited argument⟩

Case 1:

The tokens that form ⟨undelimited argument⟩ comply to the pattern

⟨optional signs⟩⟨integer constant⟩⟨one optional space⟩

In case 1

  • ⟨optional signs⟩ are converted as described below and delivered.
  • ⟨integer constant⟩ with all leading zeros removed is delivered.
    In case removal of all leading zeros yields emptiness, a single digit 012 is delivered.
  • ⟨one optional space⟩ is removed.

Case 2:

The tokens that form ⟨undelimited argument⟩ comply to the pattern

⟨optional signs⟩⟨integer constant⟩⟨decimal separator⟩⟨integer constant⟩⟨one optional space⟩

In case 2

  • ⟨optional signs⟩ are converted as described below and delivered.
  • The first/left ⟨integer constant⟩ is delivered with all leading zeros removed.
    In case removal of all leading zeros yields emptiness, a single digit 012 is delivered.
  • If removal of all trailing zeros from the second/right ⟨integer constant⟩ does not yield emptiness, then ⟨decimal separator⟩ is delivered.
  • The second/right ⟨integer constant⟩ is delivered with all trailing zeros removed.
  • ⟨one optional space⟩ is removed.

In all other cases the tokens that form the ⟨undelimited argument⟩ are delivered unchanged.
Curly braces delimiting the ⟨undelimited argument⟩ are removed.

In any case due to \romannumeral0-expansion the result is deliverd after two expansion-steps/by "hitting" \normalizenumber with \expandafter twice.

The things just said imply that, e.g., \normalizenumber{1.} returns 1. unchanged because the ⟨undelimited argument⟩ 1. neither is of the pattern described for case 1 nor is of the pattern described for case 2. \normalizenumber{1.000} yields 1—the ⟨undelimited argument⟩ 1.000 is of the pattern described for case 2.

Conversion of ⟨optional signs⟩

In case ⟨optional signs⟩ denote a non-negative number, no token at all is returned for them.
In case ⟨optional signs⟩ denote a negative number, a single explicit character token -12 is returned for them.
If the absolute value of the number to normalize is 0, you will not get a sign—you will not get -0 but you will get 0.

Expansion of \normalizenumber's ⟨undelimited argument⟩

\normalizenumber in a tail-recursive loop examines its argument token-wise: If the first token of the ⟨undelimited argument⟩ does not imply that the ⟨undelimited argument⟩ neither complies to the pattern of case 1 nor complies to the pattern of case 2, then it will be removed from the ⟨undelimited argument⟩ for the next iteration and in the next iteration \normalizenumber will "look" at the first token of the remaining ⟨undelimited argument⟩.

There is an \if-switch \ifnormalizenumberexpandarg.

If you say \normalizenumberexpandargfalse, then \normalizenumber does not expand expandable tokens during examination and encountering an expandable token implies that the ⟨undelimited argument⟩ neither does comply to the pattern described for case 1 nor does comply to the pattern described for case 2.

If you say \normalizenumberexpandargtrue, then in each iteration finding that the first token of the ⟨undelimited argument⟩ is expandable triggers "hitting" it with \expandafter and in the next iteration examining the result. Expanding the first token of the ⟨undelimited argument⟩ may affect subsequent tokens of the ⟨undelimited argument⟩.
Use \normalizenumberexpandargtrue with care and with a certain amount of suspicion:
If the first token is an unbalanced \else or \fi or an unbalanced \csname, then you can get all kinds of weird error-messages. If the first token is defined to trigger things that affect tokens beyond the closing brace of the ⟨undelimited argument⟩, then the program-flow can become unpredictable. If the first token is defined to deliver itself, you may end up in a never ending loop.

\errorcontextlines=10000
\documentclass{article}

\makeatletter
%%=============================================================================
%% Paraphernalia:
%%    \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@Removespace
%%    \UD@CheckWhetherNull, \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@Removespace{\UD@Exchange{ }{\def\UD@Removespace}{}}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
  \UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has leading
%%                        catcode-1-token>}%
%%                      {<Tokens to be delivered in case that argument
%%                        which is to be checked has no leading
%%                        catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
  \romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
  \string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
  \UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
  \UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is a
%%                               space-token>}%
%%                             {<Tokens to be delivered in case <argument
%%                               which is to be checked>'s 1st token is not
%%                               a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
  {\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%=============================================================================
%% Extract K-th inner undelimited argument:
%%
%% \UD@ExtractKthArg{<integer K>}{<list of undelimited args>} 
%% 
%% In case there is no K-th argument in <list of indelimited args> : 
%%   Does not deliver any token.
%% In case there is a K-th argument in <list of indelimited args> : 
%%   Does deliver that K-th argument with one level of braces removed.
%%
%% Examples:
%%
%%   \UD@ExtractKthArg{0}{ABCDE} yields: <nothing>
%%
%%   \UD@ExtractKthArg{3}{ABCDE} yields:  C
%%
%%   \UD@ExtractKthArg{3}{AB{CD}E} yields:  CD
%%
%%   \UD@ExtractKthArg{4}{{001}{002}{003}{004}{005}} yields: 004
%%
%%   \UD@ExtractKthArg{6}{{001}{002}{003}} yields: <nothing> 
%% 
%%=============================================================================
\newcommand\UD@ExtractKthArg[1]{%
  \romannumeral0%
  % #1: <integer number K>
  \expandafter\UD@ExtractKthArgCheck
  \expandafter{\romannumeral\number\number#1 000}%
}%
\newcommand\UD@ExtractKthArgCheck[2]{%
  \UD@CheckWhetherNull{#1}{ }{%
    \expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}{#2}%
  }%
}%
\newcommand\UD@ExtractKthArgLoop[2]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo#2{}.}{ }{%
    \UD@CheckWhetherNull{#1}{%
      \UD@ExtractFirstArgLoop{#2UD@SelDOm}%
    }{%
      \expandafter\UD@Exchange\expandafter{\expandafter{\UD@firstoftwo{}#2}}%
      {\expandafter\UD@ExtractKthArgLoop\expandafter{\UD@firstoftwo{}#1}}%
    }%
  }%
}%
\@ifdefinable\UD@RemoveTillUD@SelDOm{%
  \long\def\UD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
  {\UD@firstoftwo{\expandafter}{} \UD@secondoftwo{}#1}%
  {\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% Fork if argument, which must be a single token, is
%% 0/1/2/3/4/5/6/7/8/9/+/-/./,/<space token>/<expandable token>/<something else>
%% (total: 17 cases)
%%-----------------------------------------------------------------------------
\@ifdefinable\UD@GobbleToExclam{\long\def\UD@GobbleToExclam#1!{}}%
%%-----------------------------------------------------------------------------
\@ifdefinable\UD@normalizenumberfork{%
   \long\def\UD@normalizenumberfork#1!0!1!2!3!4!5!6!7!8!9!+!-!,!.!#2#3!!!!{#2}%
}%
\newcommand\UD@normalizenumberloopfork[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToExclam#1!}{%
    \UD@normalizenumberfork
    !#1!1!2!3!4!5!6!7!8!9!+!-!,!.!{1}% <digit> 0_12
    !0!#1!2!3!4!5!6!7!8!9!+!-!,!.!{2}% <digit> 1_12
    !0!1!#1!3!4!5!6!7!8!9!+!-!,!.!{3}% <digit> 2_12
    !0!1!2!#1!4!5!6!7!8!9!+!-!,!.!{4}% <digit> 3_12
    !0!1!2!3!#1!5!6!7!8!9!+!-!,!.!{5}% <digit> 4_12
    !0!1!2!3!4!#1!6!7!8!9!+!-!,!.!{6}% <digit> 5_12
    !0!1!2!3!4!5!#1!7!8!9!+!-!,!.!{7}% <digit> 6_12
    !0!1!2!3!4!5!6!#1!8!9!+!-!,!.!{8}% <digit> 7_12
    !0!1!2!3!4!5!6!7!#1!9!+!-!,!.!{9}% <digit> 8_12
    !0!1!2!3!4!5!6!7!8!#1!+!-!,!.!{10}% <digit> 9_12
    !0!1!2!3!4!5!6!7!8!9!#1!-!,!.!{11}% <plus or minus> +_12
    !0!1!2!3!4!5!6!7!8!9!+!#1!,!.!{12}% <plus or minus> -_12
    !0!1!2!3!4!5!6!7!8!9!+!-!#1!.!{13}% <decimal constant> ,_12
    !0!1!2!3!4!5!6!7!8!9!+!-!,!#1!{14}% <decimal constant> ._12
    !0!1!2!3!4!5!6!7!8!9!+!-!,!.!{%
      \ifcat\noexpand#1 \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
      {15}% <space token> differing from explicit character token of catcode 10 
          % and charcode 32; removable as undelimited argument
      {%
         \expandafter\ifx\noexpand#1#1%
         \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
         {18}% something else which is not allowed
         {17}% expandable token
      }%
    }% 
    !!!!%
  }{18}% Case: #1 contains !_12 , therefore is something else which is not
       % allowed
}%
%%=============================================================================
%% \normalizenumber{<argument>}
%%-----------------------------------------------------------------------------
\newcommand\normalizenumber[1]{%
  \romannumeral0%
  \normalizenumberloop{#1}{}{}{#1}{\UD@firstoftwo}{}{\UD@firstoftwo}{}%
}%
\newif\ifnormalizenumberexpandarg\normalizenumberexpandargfalse
\newcommand\normalizenumberloop[8]{%
  % #1 - argument to iterate
  % #2 - leading zero if found
  % #3 - optional minus sign
  % #4 - argument untouched
  % #5 - decimal separator not/already found - \UD@firstoftwo/\UD@secondoftwo 
  % #6 - zero-decimals collected so far
  % #7 - sign-check on/off - \UD@firstoftwo/\UD@secondoftwo
  % #8 - significant digits collected so far
  \UD@CheckWhetherNull{#1}{%
    \UD@CheckWhetherNull{#8}{\UD@CheckWhetherNull{#2}{ #4}{ #2}}{ #3#8}%
  }{%
    \UD@ExtractKthArg{%
     %-------------------------------------------------------------------------
     % \UD@ExtractKthArg's <integer K>:
     %-------------------------------------------------------------------------
      % Code for calculating \UD@ExtractKthArg's <integer K>
      \UD@CheckWhetherBrace{#1}{%
        18% argument to iterate's 1st token has catcode 1, therefore is not
          % allowed.
      }{%
        \UD@CheckWhetherLeadingSpace{#1}{%
          16% explicit character token of catcode 10 and charcode 32; not
            % removable as undelimited argument
        }{%
          \expandafter\UD@normalizenumberloopfork
          \expandafter{\romannumeral0\UD@ExtractFirstArgLoop{#1UD@SelDOm}}%
        }%
      }%
    }{%
     %-------------------------------------------------------------------------
     % \UD@ExtractKthArg's <list of undelimited args>:
     %-------------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 1st argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 1, thus #1's 
       %     1st token is <digit> 0_12
      {%
        #5{%
          \UD@CheckWhetherNull{#8}{%
            \UD@firstoftwo{%
              \expandafter\normalizenumberloop\expandafter{\UD@firstoftwo{}#1}{0}{#3}{#4}{#5}{}{\UD@secondoftwo}{#8}%
            }%
          }{\UD@Exchange{{#80}}}%
        }{%
          \UD@firstoftwo{%
            \expandafter\normalizenumberloop
            \expandafter{\UD@firstoftwo{}#1}{#2}%
            {#3}{#4}{#5}{#60}{\UD@secondoftwo}{#8}%
          }%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 2nd argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 2, thus #1's 
       %     1st token is <digit> 1_12
      {\UD@Exchange{{#8#61}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 3rd argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 3, thus #1's 
       %     1st token is <digit> 2_12
      {\UD@Exchange{{#8#62}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 4th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 4, thus #1's
       %     1st token is <digit> 3_12
      {\UD@Exchange{{#8#63}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 5th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 5, thus #1's 
       %     1st token is <digit> 4_12
      {\UD@Exchange{{#8#64}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 6th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 6, thus #1's
       %     1st token is <digit> 5_12
      {\UD@Exchange{{#8#65}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 7th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 7, thus #1's
       %     1st token is <digit> 6_12
      {\UD@Exchange{{#8#66}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 8th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 8, thus #1's
       %     1st token is <digit> 7_12
      {\UD@Exchange{{#8#67}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 9th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 9, thus #1's
       %     1st token is <digit> 8_12
      {\UD@Exchange{{#8#68}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 10th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 10, thus #1's
       %     1st token is <digit> 9_12
      {\UD@Exchange{{#8#69}}}%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 11th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 11, thus #1's
       %     1st token is <plus or minus> +_12
      {%
        \UD@firstoftwo{%
          #7{%
            \expandafter\UD@CheckWhetherNull
            \expandafter{\UD@firstoftwo{}#1}{ #4}{%
              \expandafter\normalizenumberloop
              \expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}%
            }%
          }{ #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 12th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 12, thus #1's
       %     1st token is <plus or minus> -_12
      {%
        \UD@firstoftwo{%
          #7{%
            \expandafter\UD@CheckWhetherNull
            \expandafter{\UD@firstoftwo{}#1}{ #4}{%
              \UD@CheckWhetherNull{#3}{\UD@Exchange{{-}}}{\UD@Exchange{{}}}%
              {\expandafter\normalizenumberloop\expandafter{\UD@firstoftwo{}#1}{#2}}%
              {#4}{#5}{#6}{#7}{#8}%
            }%
          }{ #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 13th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 13, thus #1's
       %     1st token is <decimal constant> ,_12
      {%
        \UD@firstoftwo{%
          #5{%
            \expandafter\UD@CheckWhetherNull
            \expandafter{\UD@firstoftwo{}#1}{ #4}{%
              \UD@CheckWhetherNull{#2#8}{ #4}{%
                \UD@CheckWhetherNull{#8}{\UD@Exchange{{#2}}}{\UD@Exchange{{#8}}}%
                {%
                  \expandafter\normalizenumberloop\expandafter{\UD@firstoftwo{}#1}%
                  {#2}{#3}{#4}{\UD@secondoftwo}{,}{\UD@secondoftwo}%
                }%
              }%
            }%
          }{ #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 14th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 14, thus #1's
       %     1st token is <decimal constant> ._12
      {%
        \UD@firstoftwo{%
          #5{%
            \expandafter\UD@CheckWhetherNull
            \expandafter{\UD@firstoftwo{}#1}{ #4}{%
              \UD@CheckWhetherNull{#2#8}{ #4}{%
                \UD@CheckWhetherNull{#8}{\UD@Exchange{{#2}}}{\UD@Exchange{{#8}}}%
                {%
                  \expandafter\normalizenumberloop\expandafter{\UD@firstoftwo{}#1}%
                  {#2}{#3}{#4}{\UD@secondoftwo}{.}{\UD@secondoftwo}%
                }%
              }%
            }%
          }{ #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 15th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 15, thus #1's
       %     1st token is a <space token> differing from explicit character
       %     token of catcode 10 and charcode 32 and is removable as
       %     undelimited argument
      {%
        \UD@firstoftwo{%
          #7{\UD@firstoftwo}{%
            \expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
          }%
          {%
            \expandafter\normalizenumberloop
            \expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}%
          }%
          { #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 16th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 16, thus #1's
       %     1st token is a <space token>, more precisely an explicit
       %     character token of catcode 10 and charcode 32 and is not removable
       %     as undelimited argument
      {%
        \UD@firstoftwo{%
          #7{\UD@firstoftwo}{%
            \expandafter\UD@CheckWhetherNull\expandafter{\UD@Removespace#1}%
          }%
          {%
            \expandafter\normalizenumberloop
            \expandafter{\UD@Removespace#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}%
          }%
          { #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 17th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 17, thus #1's 
       %     1st token is expandable.
      {%
        \UD@firstoftwo{%
          \ifnormalizenumberexpandarg
            \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
          {\expandafter\normalizenumberloop\expandafter{#1}{#2}{#3}{#4}{#5}{#6}{#7}{#8}}%
          { #4}%
        }%
      }%
       %-----------------------------------------------------------------------
       % \UD@ExtractKthArg's <list of undelimited args>'s 18th argument:
       %     \UD@ExtractKthArg's 1st argument yields the number 18, thus #1's
       %     1st token is not allowed with numbers that can be normalized.
      {%
        \UD@firstoftwo{ #4}%
      }%
     %-------------------------------------------------------------------------
     % End of \UD@ExtractKthArg's <list of undelimited args>.
     %-------------------------------------------------------------------------
    }%
    {\expandafter\normalizenumberloop\expandafter{\UD@firstoftwo{}#1}%
                            {#2}{#3}{#4}{#5}{}{\UD@secondoftwo}%
    }%
  }%
}%
%%.............................................................................
\makeatother

% Test \normalizenumber by applying it inside the definition-text of \test:

\newcommand\Test[1]{%
  \expandafter\expandafter\expandafter\def
  \expandafter\expandafter\expandafter\test
  \expandafter\expandafter\expandafter{#1}%
  \texttt{(\meaning\test)}%
}%


\makeatletter\let\sptoken= \@sptoken\makeatother

\begin{document}

\null\kern-2cm

The following either comply the pattern described in case 1 or comply the pattern described in case 2:

01: \Test{\normalizenumber{-\sptoken\sptoken-\sptoken++\sptoken00000.0000\sptoken}}

02: \Test{\normalizenumber{-\sptoken\sptoken-\sptoken++\sptoken - 8\sptoken}}

03: \Test{\normalizenumber{+-+00000}}

04: \Test{\normalizenumber{-++++0}}

05: \Test{\normalizenumber{---00000.000010000}}

06: \Test{\normalizenumber{--+-0003.9}}

07: \Test{\normalizenumber{+-+00087}}

08: \Test{\normalizenumber{+ -+00024}}

09: \Test{\normalizenumber{--87.0000}}

10: \Test{\normalizenumber{+--0015.00000010000700000}}

11: \Test{\normalizenumber{+98.0000 }}

12: \Test{\normalizenumber{4.50000}}

13: \Test{\normalizenumber{2.50000 }}

14: \Test{\normalizenumber{7,4}}

15: \Test{\normalizenumber{67}}

16: \Test{\normalizenumber{-15}}

17: \Test{\normalizenumber{-+  +-+ 15 }}

18: \Test{\normalizenumber{67,0000}}

19: \Test{\normalizenumber{67,0000001}}

20: \Test{\normalizenumber{68,0000 }}

21: \Test{\normalizenumber{2,80000}}

22: \Test{\normalizenumber{7,50000 }}

23: \Test{\normalizenumber{1,50000 }}

\kern\dp\strutbox

\hrule

\kern\dp\strutbox

The following don't comply any of these two patterns:

24: \Test{\normalizenumber{}}

25: \Test{\normalizenumber{--++}}

26: \Test{\normalizenumber{--++}}

27: \Test{\normalizenumber{-1.}}

28: \Test{\normalizenumber{3.7.0000 }}

29: \Test{\normalizenumber{8,5,0000 }}

30: \Test{\normalizenumber{8,9.0000 }}

31: \Test{\normalizenumber{9.3,0000 }}

32: \Test{\normalizenumber{A.0000}}

33: \Test{\normalizenumber{1{1}1}}

34: \Test{\normalizenumber{{1},6}}

35: \Test{\normalizenumber{1,}}

36: \Test{\normalizenumber{7,~ / 8()}}

37: \Test{\normalizenumber{1{1}1}}

\kern\dp\strutbox

\hrule

\kern\dp\strutbox

\verb|\def\macroa#1#2{- - + -00012\macrob}%|
\def\macroa#1#2{- - + -00012\macrob}%

\verb|\def\macrob{34.56000}%|
\def\macrob{34.56000}%

\verb|\normalizenumberexpandargfalse|
\normalizenumberexpandargfalse

32: \Test{\normalizenumber{\macroa{7}{8}}}

\verb|\normalizenumberexpandargtrue|
\normalizenumberexpandargtrue

33: \Test{\normalizenumber{\macroa{7}{8}}}

\end{document}

enter image description here

2
  • Your answers never cease to both amaze and scare me :-)
    – campa
    Commented Sep 23, 2020 at 6:43
  • @campa Often things, if you name them, are not so scary anymore - so that's what I do now: From time to time I am carried away by minor seizures of a disease which is named "macro madness" in TeXBook - "Appendix D: Dirty Tricks" - item 1. ;-) There is also a remark that disappears from my mind from time to time: "Always remember, however, that there’s usually a simpler and better way to do something than the first way that pops into your head. You may not have to resort to any subterfuge at all, since TeX is able to do lots of things in a straightforward way. Try for simple solutions first." Commented Sep 23, 2020 at 10:15

You must log in to answer this question.