Checking if argument is a floating point without breaking on control sequences in argument

The code needs a little more token handling than the simpler case for integers. I made the function expandable, so this adds a little bit more code too, but it's not that much.

First the function uses \detokenize to ensure that TeX doesn't try to expand anything weird later on. After that the code proceeds removing a possible integer part with a possible sign using \romannumeral-0#1 (and some other things), then the code removes a possible decimal separator, and removes the trailing decimal part. After that the code checks whether the remaining token list is empty. If it is, then the argument was a valid number, otherwise it was not.

Testing a few possibilities, returns the desired output:

enter image description here

The code being expandable means that you can do:

\def\TestIfIsAPositiveNumber#1{%
  \ifdim
    \TestNumber{#1}{#1}{-1}pt
      > 0pt
    Positive :)
  \else
    Negative or weird :(
  \fi
}

and get the expected output.

Here's the code:

\documentclass{article}
\makeatletter
\def\TestNumber#1{%
  \Test@ifempty{#1}%
    {\@secondoftwo}%
    {\expandafter\Test@integer\expandafter{\detokenize{#1}}}}%
\def\Test@integer#1{%
  \expandafter\Test@after@integer\expandafter{%
    \romannumeral-0\expandafter\Test@remove@leading@minus\expandafter{%
      \romannumeral-0\number0#1}}}
\def\Test@after@integer#1{%
  \expandafter\Test@ifempty\expandafter{%
    \romannumeral-0\Test@remove@leading@dot{#1}}}
\def\Test@remove@leading@minus#1{%
  \Test@remove@leading-{#1}}
\def\Test@remove@leading@dot#1{%
  \Test@remove@leading.{#1}}
\def\Test@remove@leading#1#2{%
  \Test@ifempty{#2}{}%
    {\Test@@remove@leading#1#2\qstop}}
\def\Test@@remove@leading#1#2#3\qstop{%
  \if\noexpand#1\noexpand#2%
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi{#3}{#2#3}}
\def\Test@ifempty#1{%
  \if\relax\detokenize{#1}\relax
    \expandafter\@firstoftwo
  \else
   \expandafter\@secondoftwo
  \fi}
\makeatother
\begin{document}
\def\test#1{\texttt{\detokenize{#1} = }\TestNumber{#1}{Number}{Not a number}\par}
\test{0}
\test{1}
\test{-1}
\test{.23}
\test{-.23}
\test{1.23}
\test{-1.23}
\test{\textbf{1.23}}
\end{document}

Or, a simpler (but unexpandable) version with l3regex (the expression was copied from interface3 and changed the control spaces \␣ by \s, which matches [\ \^^I\^^J\^^L\^^M], according to the manual):

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\regex_const:Nn \c_jessepeng_float_regex { ^[\+\-\s]*(\d+|\d*\.\d+)\s*$ }
\NewDocumentCommand \TestNumber { m m m }
  { \jessepeng_if_float:nTF {#1} {#2} {#3} }
\prg_new_protected_conditional:Npnn \jessepeng_if_float:n #1 { T, F, TF }
 {
   \regex_match:NnTF \c_jessepeng_float_regex {#1}
     { \prg_return_true: }
     { \prg_return_false: }
 }
\ExplSyntaxOff
\begin{document}
\def\test#1{\texttt{\detokenize{#1} = }\TestNumber{#1}{Number}{Not a number}\par}
\test{0}
\test{1}
\test{-1}
\test{.23}
\test{-.23}
\test{1.23}
\test{-1.23}
\test{\textbf{1.23}}
\end{document}

It yields the same result but is much slower and is not expandable.


Borrowing a test file from the other answer, as you failed to supply one in the question, for pdflatex at least you can use its built in regex support

enter image description here

\documentclass{article}

\makeatletter
\def\TestNumber#1{%
\ifnum1=\pdfmatch{^\string\s*-?[0-9]*[.0-9][0-9]*\string\s*$}{\detokenize{#1}}
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}


\begin{document}
\def\test#1{\texttt{\detokenize{#1} = }\TestNumber{#1}{Number}{Not a number}\par}
\test{}
\test{0}
\test{1}
\test{-1}
\test{.23}
\test{-.23}
\test{1.23}
\test{-1.23}
\test{\textbf{1.23}}
\end{document}

or a version using Lua patterns for luatex

\documentclass{article}

\makeatletter
\ifx\pdfmatch\@undefined
\ifx\directlua\@undefined
% xetex
\typeout{use l3regex from the other answer}
\else
% luatex
\def\TestNumber#1{%
\ifnum1=\directlua{if (string.find("\luaescapestring{\detokenize{#1}}","^[-]?\@percentchar d*[.]?\@percentchar d+$"))
then tex.write("1 ")
else tex.write("0 ")
end
}
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
\fi
\else
% pdftex
\def\TestNumber#1{%
\ifnum1=\pdfmatch{^\string\s*-?[0-9]*[.0-9][0-9]*\string\s*$}{\detokenize{#1}}
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
\fi
\makeatother


\begin{document}
\def\test#1{\texttt{\detokenize{#1} = }\TestNumber{#1}{Number}{Not a number}\par}
\test{}
\test{0}
\test{1}
\test{-1}
\test{.23}
\test{-.23}
\test{1.23}
\test{-1.23}
\test{\textbf{1.23}}
\end{document}