3

I am searching for a reliable way to determine whether a String can be fed into \pgfmathprintnumber. I used to use the \IfDecimal from the xstring package which works fine for things like 3.14 and similar inputs. However \pgfmathprintnumber can deal with scientific notation that will be sorted out by \IfDecimal because it thinks that it is not a number.

Here's an MWE:

\documentclass{article}

\usepackage{pgf}
\usepackage{xstring}

\begin{document}
    \def\testNum{3E+64}

    \IfDecimal{\testNum}{
        \pgfmathprintnumber[sci]{\testNum} is a number
    }{
        \pgfmathprintnumber[sci]{\testNum} is not a number
    }
\end{document}

As you'll see the number is properly recognized by \pgfmathprintnumber but not by \IfDecimal.

(I also tried the \DTLisnumerical command from the datatool package but it doesn't work either).

Does someone know a macro that will also detect scientific notations as numbers (preferably a validation by PGF itself - something that will try to parse the string with the mathematical engine but simply exits if the string can't be parsed as a number instead of killing LaTeX with an error)?

3

Okay so I did some extensive web-research and stumbled upon this question which had an interesting approach in the last answer. Furthermore I found this post.

As can be seen in the first linked question there is a key /pgf/fpu/handlers/invalid number/.code that defines the behaviour if a bad number format in \pgfmathfloatparsenumber is encountered. To quote the manual:

This command key is invoked in case an invalid string is parsed inside of \pgfmathfloatparsenumber. You can overwrite it to assign a replacement \pgfmathresult (in float!). The initial setting is to generate an error message.

From the second question I found out that there are flags indicating whether the parsed number was valid or not. So I combined the two of them to get

\documentclass{article}

\usepackage{pgf}
\usepgflibrary{fpu}

\newcommand{\isPGFNumber}[3]{% \isPGFNumber{<Number>}{<True>}{<False>}
    \begingroup
    \pgfkeys{/pgf/fpu/handlers/invalid number/.code={}}%
    \pgfmathfloatparsenumber{#1}%
    \global\pgfmathfloatgetflagstomacro\pgfmathresult\pgfFloatParseFlag%
    \endgroup
    \ifnum\pgfFloatParseFlag=3%
        #3%
    \else%
        #2%
    \fi%
}

\begin{document}
    \isPGFNumber{3}{T}{F}\\
    \isPGFNumber{3.23}{T}{F}\\
    \isPGFNumber{.3}{T}{F}\\
    \isPGFNumber{3E+12}{T}{F}\\
    \isPGFNumber{3e-8}{T}{F}\\
    \isPGFNumber{3z}{T}{F}\\
    \isPGFNumber{Is there a problem?}{T}{F}\\\\

    %\pgfmathfloatparsenumber{z}
\end{document}

The \begingroup and \endgroup inside the macro definition is there in order to go back to the normal error-on-invalid-numbers-behaviour after having called the macro (Which can be tested by uncommenting the last statement in the document). As you can see it properly deals with the numbers even if provided in scientific format.

3

You can do it with regular expressions.

\documentclass{article}
\usepackage{xparse}
\usepackage{siunitx} % for the test

\ExplSyntaxOn

\prg_new_protected_conditional:Nnn \raven_if_fpnumber:n { T,F,TF }
 {
  \regex_match:nnTF { [eE] } { #1 }
   {% there is e or E
    \regex_match:nnTF
     {
      % optional sign,
      % optional integer part,
      % optional period,
      % optional fractional part,
      % one e or E
      % optional sign
      % mandatory integer
      \A
      [-+]?([0-9]+(\.[0-9]+)?|\.[0-9]+)
      [eE]{1}
      [-+]?[0-9]+
      \Z
     }
     { #1 } { \prg_return_true: } { \prg_return_false: }
   }
   {% no e nor E
    \regex_match:nnTF
     {
      \A
      [-+]?([0-9]+(\.[0-9]+)?|\.[0-9]+)
      \Z
     }
     { #1 } { \prg_return_true: } { \prg_return_false: }
   }
 }
\prg_generate_conditional_variant:Nnn \raven_if_fpnumber:n { x } { T,F,TF }

\NewDocumentCommand{\isfpnumberTF}{mmm}
 {
  \raven_if_fpnumber:xTF { #1 } { #2 } { #3 }
 }

\ExplSyntaxOff

% for the test
\newcommand{\test}[1]{%
  \texttt{\detokenize\expandafter{#1}}:
  \isfpnumberTF{#1}{\num{#1}}{``#1'' is not a number}%
}
\newcommand{\anumber}{3E+64}

\begin{document}

\test{\anumber}

\test{3}

\test{3.23}

\test{.3}

\test{3E+12}

\test{3e-8}

\test{3z}

\test{Is there a problem?}

\end{document}

enter image description here

2

The following builds upon tests I've implemented elsewhere. I did not remove everything unnecessary for this answer to work, so it might add more stuff than necessary. The test for a group in the argument is from Check for group as argument (expandable).

The test should be fully expandable.

\documentclass[]{article}

\makeatletter
\def\endMYifargument{\endMYifargument}
\newcommand\MYif@endMYifargument[1]% >>>
  {%
    \ifx\endMYifargument#1%
      \MYif@fiAa
    \else
      \MYif@fiBb
    \fi
  }% <<<
% MYif@fi macros >>>
\long\def\MYif@fiAa#1\fi#2#3{\fi#2}
\long\def\MYif@fiAb#1\fi#2#3{\fi#3}
\long\def\MYif@fiBa\fi#1#2{\fi#1}
\long\def\MYif@fiBb\fi#1#2{\fi#2}
\long\def\MYif@fiAy#1\fi#2{\fi#2}
\long\def\MYif@fiAn#1\fi#2{\fi}
\long\def\MYif@fiBy\fi#1{\fi#1}
\long\def\MYif@fiBn\fi#1{\fi}
\long\def\MYif@fifiAa#1\fi\fi#2#3{\fi#2}
\long\def\MYif@fifiAb#1\fi\fi#2#3{\fi#3}
\long\def\MYif@fifiBAa#1\fi\fi#2#3{\fi\fi#2}
\long\def\MYif@fifiBAb#1\fi\fi#2#3{\fi\fi#3}
\long\def\MYif@fifiBBa\fi\fi#1#2{\fi\fi#1}
\long\def\MYif@fifiBBb\fi\fi#1#2{\fi\fi#2}
% <<<
\newcommand*\MYif@groupify@TF[1]% >>>
  {%
    \expandafter\long\expandafter\edef\csname MYif#1GTF\endcsname##1%
      {%
        \unexpanded{\MYifGroupTF}{##1}%
          {\unexpanded\expandafter{\csname MYif#1GTF\endcsname}##1}%
          {\unexpanded\expandafter{\csname MYif#1TF\endcsname}{##1}}%
      }%
  }% <<<
\newcommand*\MYif@create@TandF[1]% >>>
  {%
    \expandafter\long\expandafter\edef\csname MYif#1T\endcsname##1%
      {%
        \unexpanded\expandafter{\csname MYif#1TF\endcsname}{##1}%
          {\noexpand\@firstofone}
          {\noexpand\@gobble}%
      }%
    \expandafter\long\expandafter\edef\csname MYif#1F\endcsname##1%
      {%
        \unexpanded\expandafter{\csname MYif#1TF\endcsname}{##1}%
          {\noexpand\@gobble}
          {\noexpand\@firstofone}%
      }%
  }% <<<
\newcommand*\MYif@create@TandF@twoargs[1]% >>>
  {%
    \expandafter\long\expandafter\edef\csname MYif#1T\endcsname##1##2%
      {%
        \unexpanded\expandafter{\csname MYif#1TF\endcsname}{##1}{##2}%
          {\noexpand\@firstofone}
          {\noexpand\@gobble}%
      }%
    \expandafter\long\expandafter\edef\csname MYif#1F\endcsname##1##2%
      {%
        \unexpanded\expandafter{\csname MYif#1TF\endcsname}{##1}{##2}%
          {\noexpand\@gobble}
          {\noexpand\@firstofone}%
      }%
  }% <<<
\newcommand*\MYif@create@N@TandF[1]% >>>
  {%
    \expandafter\long\expandafter\edef\csname MYif#1NTF\endcsname##1%
      {%
        \noexpand\expandafter
        \expandafter\noexpand\csname MYif#1TF\endcsname
        \noexpand\expandafter{##1}%
      }%
    \MYif@create@TandF{#1N}%
  }% <<<
\newcommand*\MYif@create@N@TandF@twoargs[1]% >>>
  {%
    \expandafter\long\expandafter\edef\csname MYif#1NNTF\endcsname##1##2%
      {%
        \noexpand\expandafter
        \expandafter\noexpand\csname MYif#1NNTF@a\endcsname
        \noexpand\expandafter{##2}{##1}%
      }%
    \expandafter\long\expandafter\edef\csname MYif#1NNTF@a\endcsname##1##2%
      {%
        \noexpand\expandafter
        \expandafter\noexpand\csname MYif#1TF\endcsname
        \noexpand\expandafter{##2}{##1}%
      }%
    \MYif@create@TandF@twoargs{#1NN}%
    \expandafter\long\expandafter\edef\csname MYif#1NnTF\endcsname##1##2%
      {%
        \noexpand\expandafter
        \expandafter\noexpand\csname MYif#1TF\endcsname
        \noexpand\expandafter{##1}{##2}%
      }%
    \MYif@create@TandF@twoargs{#1Nn}%
    \expandafter\long\expandafter\edef\csname MYif#1nNTF\endcsname##1##2%
      {%
        \noexpand\expandafter
        \expandafter\noexpand\csname MYif#1nNTF@a\endcsname
        \noexpand\expandafter{##2}{##1}%
      }%
    \expandafter\long\expandafter\edef\csname MYif#1nNTF@a\endcsname##1##2%
      {%
        \expandafter\noexpand\csname MYif#1TF\endcsname{##2}{##1}
      }%
    \MYif@create@TandF@twoargs{#1nN}%
  }% <<<
\newcommand*\MYif@create@TandF@N@TandF[1]% >>>
  {%
    \MYif@create@TandF{#1}%
    \MYif@create@N@TandF{#1}%
  }% <<<
\newcommand*\MYif@create@TandF@N@TandF@twoargs[1]% >>>
  {%
    \MYif@create@TandF@twoargs{#1}%
    \MYif@create@N@TandF@twoargs{#1}%
  }% <<<
\newcommand\MYif@notEmpty@noGroup@noSpace[2]% >>>
  {%
    \MYifEmptyTF{#1}
      {\@secondoftwo}
      {%
        \MYifContainsGroupTF{#1}
          {\@secondoftwo}
          {%
            \MYifContainsSpaceTF{#1}
              {\@secondoftwo}
              {#2}%
          }%
      }%
  }% <<<
% MYifEmpty >>>
\newcommand\MYifEmptyTF[1]% >>>
  {%
    \if\relax\detokenize{#1}\relax
      \MYif@fiAa
    \else
      \MYif@fiBb
    \fi
  }% <<<
\newcommand\MYifEmptyT[1]{% >>>
  \if\relax\detokenize{#1}\relax
    \MYif@fiAy
  \else
    \MYif@fiBn
  \fi}
% <<<
\newcommand\MYifEmptyF[1]{% >>>
  \if\relax\detokenize{#1}\relax
    \MYif@fiAn
  \else
    \MYif@fiBy
  \fi}
% <<<
\newcommand\MYifEmptyNTF[1]% >>>
  {\expandafter\MYifEmptyTF\expandafter{#1}}% <<<
\newcommand\MYifEmptyNT[1]% >>>
  {\expandafter\MYifEmptyT\expandafter{#1}}% <<<
\newcommand\MYifEmptyNF[1]% >>>
  {\expandafter\MYifEmptyF\expandafter{#1}}% <<<
\MYif@groupify@TF{Empty}
\MYif@create@TandF@N@TandF{EmptyG}
% <<<
% MYifContainsGroup >>>
\newcommand\MYifContainsGroupTF[1]% >>>
  {%
    \expandafter\MYifContainsGroup@a
      \MYifContainsGroup@eat#1{}%
    \MYifContainsGroup@endargument
  }% <<<
\long\def\MYifContainsGroup@eat#1#{}
\long\def\MYifContainsGroup@a#1\MYifContainsGroup@endargument% >>>
  {%
    \MYifEmptyTF{#1}
      {\@secondoftwo}
      {\@firstoftwo}%
  }% <<<
\MYif@groupify@TF{ContainsGroup}
\MYif@create@TandF@N@TandF{ContainsGroup}
\MYif@create@TandF@N@TandF{ContainsGroupG}
% <<<
% MYifContainsSpace >>>
\newcommand\MYifContainsSpaceTF[1]% >>>
  {%
    \expandafter\MYifContainsSpace@a
      \MYifContainsSpace@eat#1
    \MYifContainsSpace@endargument
  }% <<<
\long\def\MYifContainsSpace@a#1\MYifContainsSpace@endargument% >>>
  {%
    \MYifEmptyTF{#1}
      {\@secondoftwo}
      {\@firstoftwo}%
  }% <<<
\long\def\MYifContainsSpace@eat#1 {}
\MYif@groupify@TF{ContainsSpace}
\MYif@create@TandF@N@TandF{ContainsSpace}
\MYif@create@TandF@N@TandF{ContainsSpaceG}
% <<<
% MYifDigit >>>
\newcommand\MYifDigitTF[1]% >>>
  {%
    \MYifOneTokenNoGroupTF{#1}
      {\MYifDigit@a{#1}}
      {\@secondoftwo}%
  }% <<<
\newcommand\MYifDigit@a[1]% >>>
  {%
    \ifx0#1\MYif@fiAa
    \else\MYifDigit@b1#1%
    \else\MYifDigit@b2#1%
    \else\MYifDigit@b3#1%
    \else\MYifDigit@b4#1%
    \else\MYifDigit@b5#1%
    \else\MYifDigit@b6#1%
    \else\MYifDigit@b7#1%
    \else\MYifDigit@b8#1%
    \else\MYifDigit@b9#1%
    \else\MYif@fiBb
    \fi
  }% <<<
\newcommand\MYifDigit@b[2]% >>>
  {%
    \fi\ifx#1#2\MYif@fiAa
  }% <<<
\newcommand\MYifDigitGTF[1]% >>>
  {%
    \MYifGroupTF{#1}
      {\MYifDigitGTF#1}
      {%
        \MYifOneTokenTF{#1}
          {\MYifDigit@a{#1}}
          {\@secondoftwo}%
      }%
  }% <<<
\MYif@create@TandF@N@TandF{Digit}
\MYif@create@TandF@N@TandF{DigitG}
% <<<
% MYifNumber >>>
\newcommand\MYifNumberTF[1]% >>>
  {%
    \MYif@notEmpty@noGroup@noSpace{#1}{\MYifNumber@ifSign#1\endMYifargument}%
  }% <<<
\newcommand\MYifNumber@ifSign[1]% >>>
  {%
    \ifx+#1%
      \MYif@fifiAa
    \else
      \ifx-#1%
        \MYif@fifiBAa
      \else
        \MYif@fifiBBb
      \fi
    \fi
    {\MYifNumber@ifDigit@noend}
    {\MYifNumber@ifDigit#1}
  }% <<<
\newcommand\MYifNumber@ifDigit[1]% >>>
  {%
    \MYif@endMYifargument{#1}
      {\@firstoftwo}
      {%
        \MYifDigit@a{#1}
          {\MYifNumber@ifDigit}
          {\MYifNumber@false}%
      }%
  }% <<<
\newcommand\MYifNumber@ifDigit@noend[1]% >>>
  {%
    \MYif@endMYifargument{#1}
      {\@secondoftwo}
      {%
        \MYifDigit@a{#1}
          {\MYifNumber@ifDigit}
          {\MYifNumber@false}%
      }%
  }% <<<
\long\def\MYifNumber@false#1\endMYifargument% >>>
  {%
    \@secondoftwo
  }% <<<
\MYif@groupify@TF{Number}
\MYif@create@TandF@N@TandF{Number}
\MYif@create@TandF@N@TandF{NumberG}
% <<<
% MYifFloat >>>
\newcommand\MYifFloatTF[1]% >>>
  {%
    \MYif@notEmpty@noGroup@noSpace{#1}{\MYifFloat@a{#1}}
  }% <<<
%\newcommand\MYifFloat@a \newcommand\MYifFloat@ifDot >>>
\begingroup
\catcode`\?=4
\def\MYif@zz
  {%
    \endgroup
    \newcommand\MYifFloat@a[1]% >>>
      {%
        \MYifFloat@ifDot{##1}
          {\MYifFloat@withDot##1\MYifFloat@endargument}
          {\MYifNumber@ifSign##1\endMYifargument}%
      }% <<<
    \newcommand\MYifFloat@ifDot[1]% >>>
      {%
        \MYifFloat@ifDot@a##1.?\MYifFloat@endargument
      }% <<<
    \long\def\MYifFloat@ifDot@a##1.##2\MYifFloat@endargument% >>>
      {%
        \ifx?##2%
          \MYif@fiAb
        \else
          \MYif@fiBa
        \fi
      }% <<<
  }
\MYif@zz
% <<<
\long\def\MYifFloat@withDot#1.#2\MYifFloat@endargument% >>>
  {%
    \MYifEmptyTF{#1}
      {\MYifNumber@ifDigit@noend#2\endMYifargument}
      {%
        \MYifNumber@ifSign#1\endMYifargument
          {\MYifNumber@ifDigit#2\endMYifargument}
          {\@secondoftwo}%
      }%
  }% <<<
\MYif@groupify@TF{Float}
\MYif@create@TandF@N@TandF{Float}
\MYif@create@TandF@N@TandF{FloatG}
% <<<
% MYifContainsE >>>
\newcommand\MYifContainseTF[1]% >>>
  {%
    \MYifContainsE@e#1e\MYifContainsE@end
      {\@secondoftwo}
      {\@firstoftwo}%
  }% <<<
\long\def\MYifContainsE@e#1e#2\MYifContainsE@end% >>>
  {%
    \MYifEmptyTF{#2}%
  }% <<<
\newcommand\MYifContainsETF[1]% >>>
  {%
    \MYifContainsE@E#1E\MYifContainsE@end
      {\@secondoftwo}
      {\@firstoftwo}%
  }% <<<
\long\def\MYifContainsE@E#1E#2\MYifContainsE@end% >>>
  {%
    \MYifEmptyTF{#2}%
  }% <<<
% <<<
% MYifScientific >>>
\newcommand\MYifScientificTF[1]% >>>
  {%
    \MYifContainsETF{#1}
      {\MYifScientific@withE#1\endMYifScientific}
      {%
        \MYifContainseTF{#1}
          {\MYifScientific@withe#1\endMYifScientific}
          {\MYifFloatTF{#1}}%
      }%
  }% <<<
\long\def\MYifScientific@withE#1E#2\endMYifScientific% >>>
  {%
    \MYifFloatTF{#1}
      {\MYifNumberTF{#2}}
      {\@secondoftwo}%
  }% <<<
\long\def\MYifScientific@withe#1e#2\endMYifScientific% >>>
  {%
    \MYifFloatTF{#1}
      {\MYifNumberTF{#2}}
      {\@secondoftwo}%
  }% <<<
\MYif@groupify@TF{Scientific}
\MYif@create@TandF@N@TandF{Scientific}
\MYif@create@TandF@N@TandF{ScientificG}
% <<<

\makeatother

\usepackage{pgf}

\begin{document}
\def\testNum{3e+64}

\MYifScientificNTF{\testNum}
  {%
    \pgfmathprintnumber[sci]{\testNum} is a number
  }
  {%
    \pgfmathprintnumber[sci]{\testNum} is not a number
  }
\end{document}

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.