7

I wish to build a (simple) implementation of abs. I tried \FPabs but I get a lot of zeros after the decimal point. I could get rid of them with the FP round function, but I don't know the number of decimals I will have.

I think a string manipulation approach is better. I could use the xstring package to get rid of the minus as in -12.3 to get 12.3 but a full package just for that ...

Is there a simple way to get rid or consume the "-" of "-12.3" for instance to get "12.3" ?

6

You can define \gobbleminus by this way:

\def\gobbleminus#1{\ifx-#1\else#1\fi}

%test:
\gobbleminus -12

\gobbleminus 1234

\gobbleminus .24
1
  • Elegant way to get rid of the "-" as looked for ! – user1771398 Feb 16 at 2:49
11

You could use xfp (the package is very short, it contains only two definitions to create wrappers around \fp_eval:n and \int_eval:n):

\documentclass{article}
\usepackage{xfp}
\begin{document}

\fpeval{abs(-12.3)}

\end{document}

enter image description here

It is expandable, that means you can fed it to \num for formatting. Here I change the period to a command:

\documentclass{article}
\usepackage{xfp,siunitx}

\sisetup{locale=DE}
\begin{document}

\num{\fpeval{abs(-12.3)}}

\end{document}

enter image description here

1
  • Nice solution, I immediately adopted before wipet’s solution came out. I will retain it for other functions. Thanks. – user1771398 Feb 17 at 8:02
5

I'm sure you get a LaTeX3 solution by one of the experts in no time and I really don't think that a string-based solution is the best you can do here, but while you wait here is a simple, argument-based solution.

The solution is not expandable, which might be a bit of deal-breaker depending on what you want to do (but: see below).

The main idea is to use delimited arguments (How does TeX look for delimited arguments?) to strip characters from a string. In order not to run into trouble here, we first check whether the characters are actually present.

\ifstrstartswith works by defining a helper macro \fstr@ifstrstartswith@i that grabs arguments as follows

#1<characters to find>#2&

if we now pass a string to \fstr@ifstrstartswith@i that starts with <characters to find>, TeX's argument parse rules make #1 come up empty. In order to avoid errors, we always call \fstr@ifstrstartswith@i with \fstr@ifstrstartswith@i <string><characters to find>&. This means that even if <string> does not contain <characters to find> at all, we still have called the macro with the right argument signature.

The characters stripping now works similarly. We check that <string> actually starts with <characters to remove> and then apply a helper macro \fstr@stripfromstart@i with signature

<characters to remove>#1&

which then strips off the <characters to remove> from the beginning of <string> when called as \fstr@stripfromstart@i <string>&.

\documentclass[british]{article}
\usepackage[T1]{fontenc}
\usepackage{babel}

\usepackage{etoolbox}

\makeatletter
% {<characters to find>}{<string>}
\newrobustcmd*{\ifstrstartswith}[2]{%
  \def\fstr@ifstrstartswith@i##1#1##2&{%
    \ifblank{##1}}%
  \fstr@ifstrstartswith@i#2#1&}

% {<characters to remove>}{<string>}
\newrobustcmd*{\stripfromstart}[2]{%
  \def\fstr@stripfromstart@i#1##1&{##1}%
  \ifstrstartswith{#1}{#2}
    {\fstr@stripfromstart@i#2&}
    {#2}}
\makeatother

\newcommand*{\mysimpleabs}{\stripfromstart{-}}

\begin{document}
\mysimpleabs{4.5}

\mysimpleabs{-4.5}
\end{document}

4.5//4.5

Of course you don't want to try \mysimpleabs{-4.5-5} or \mysimpleabs{-4.5+5}.


edit Come to think of it, we can make this expandable if we hard-code the -. Still, string manipulation does not strike me as the best way to deal with this.

\documentclass[british]{article}
\usepackage[T1]{fontenc}
\usepackage{babel}

\usepackage{etoolbox}

\makeatletter
\newcommand*{\fstr@ifstrstartswithminus@i}{} % just to reserve the name
\def\fstr@ifstrstartswithminus@i#1-#2&{\ifblank{#1}}

\newcommand*{\ifstrstartswithminus}[1]{%
  \fstr@ifstrstartswithminus@i#1-&}

\newcommand*{\fstr@stripminusfromstart@i}{} % just to reserve the name
\def\fstr@stripminusfromstart@i-#1&{#1}

\newcommand*{\stripminusfromstart}[1]{%
  \ifstrstartswithminus{#1}
    {\fstr@stripminusfromstart@i#1&}
    {#1}}
\makeatother

\newcommand*{\mysimpleabs}{\stripminusfromstart}

\begin{document}
\mysimpleabs{4.5}

\mysimpleabs{-4.5}

\edef\foo{\mysimpleabs{12.3}}
\meaning\foo

\edef\foo{\mysimpleabs{-12.3}}
\meaning\foo
\end{document}
2
  • Why do you think a string approach shouldn't be used here ? What are the drawbacks ? – user1771398 Feb 16 at 2:56
  • 1
    @user1771398 The most obvious problem is that it doesn't produce the mathematically correct result for more complex terms like \mysimpleabs{-4.5-5}. Plus it feels a bit intellectually dishonest to use string operations to manipulate numbers. But if you can guarantee that you will only ever input a single number which either does or does not start with - the approach might still work well enough. – moewe Feb 16 at 6:05
3

If you know that the thing you're removing the minus from starts with a minus or a digit, then the old school way of doing this would be

\makeatletter
\def\gobbleminus#1{\ifnum#17<\z@ \@firstoftwo\fi #1}
\makeatother

e.g.

\gobbleminus 12.3    % Expands to 12.3
\gobbleminus -12.3   % Expands to 12.3
\gobbleminus -0.1000 % Expands to 0.1000

If your data is being generated by expanding something, you will of course need to make sure it gets sufficiently expanded before letting \gobbleminus act on it.

Another catch is that

\gobbleminus .123    % Raises error

since . is not a minus or digit. Though it occurs to me that one could make it tolerate this as well, by changing the definition to

\makeatletter
\def\gobbleminus#1{\ifdim#17pt<\z@ \@firstoftwo\fi #1}
\makeatother

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.