Unable to pass result of one command as argument to another

In my document I have the need to do a little bit of maths, which is related to sizing images. Specifically I need a linear interpolation function, the inverse function of linear interpolation, and a clamping function.

I have constructed these functions, as well as a document to test them:

\documentclass[twoside]{memoir}
\usepackage[papersize={8.5in,11in}, vmargin=0.5in, outer=1in, inner=0.5in, includehead, includefoot]{geometry}
\usepackage{fp}

\makeatletter

\newcommand\clamp[3] {
\def\clamped{#1}
\ifdim #1pt>#3pt
\def\clamped{#3}
\fi
\ifdim #1pt<#2pt
\def\clamped{#2}
\fi
\clamped
}

\newcommand\unlerp[3] {
\FPsub\numerator{\number #1}{\number #2}
\FPsub\denominator{\number #3}{\number #2}
\FPdiv\result{\numerator}{\denominator}
\result
}

\newcommand\lerp[3] {
\FPsub\result{\number #3}{\number #2}
\FPmul\result{\number #1}{\result}
\result
}

\makeatother

\begin{document}

\huge

\bfseries Clamp Tests
\normalfont

Clamp -5 between 0 and 100: \clamp{-5}{0}{100}

Clamp 45 between 0 and 100: \clamp{45}{0}{100}

Clamp 150 between 0 and 100: \clamp{150}{0}{100}

Clamp -0.5 between 0 and 1: \clamp{-0.5}{0}{1}

Clamp 0.5 between 0 and 1: \clamp{0.5}{0}{1}

Clamp 1.5 between 0 and 1: \clamp{1.5}{0}{1}

\bfseries Lerp Tests
\normalfont

Lerp 0 between 0 and 100: \lerp{0}{0}{100}

Lerp 0.5 between 0 and 100: \lerp{0.5}{0}{100}

Lerp 1 between 0 and 100: \lerp{1}{0}{100}

\bfseries Unlerp Tests
\normalfont

Unlerp 0 between 0 and 100: \unlerp{0}{0}{100}

Unlerp 45 between 0 and 100: \unlerp{45}{0}{100}

Unlerp 150 between 0 and 100: \unlerp{100}{0}{100}

Unlerp 0.5 between 0 and 2: \unlerp{0.5}{0}{2}

\bfseries Combination Tests
\normalfont

Store manual value in variable: \def\testa{0.500000000000000000}

Use manual variable in clamp: \clamp{\testa}{0}{1}

Store unlerp result in variable: \def\testb{\unlerp{50}{0}{100}}

Output unlerp result variable: \testb

%Variable -> clamp: \clamp{\testb}{0}{1}
% Fails with the following error messages:
%  ! Missing number, treated as zero.
%  ! Illegal unit of measure (pt inserted).
%  ! Missing = inserted for \ifdim.

%Unlerp -> Clamp: \clamp{\unlerp{50}{0}{100}}{0}{1}
% Fails with the following error messages:
%  ! Missing number, treated as zero.
%  ! Illegal unit of measure (pt inserted).
%  ! Missing = inserted for \ifdim.

\end{document}


In isolation they seem to work, but I'm having difficulty combining them. If I try to call them by nesting their arguments I get an error. I can store the output of one using \def and then display it on the page, but as soon as I try to pass it to the next command I get the same error. The failing calls can be seen at the bottom of my example, commented out.

Missing number, treated as zero.

Illegal unit of measure (pt inserted).

Missing = inserted for \ifdim.


(Plus a bunch more error messages that are repetitions of the above)

What am I doing wrong?

I would suggest using fp-commands from the upcoming LaTeX3-project, not to be confused with the package fp you are using. The fp-package is old and clunky.

The following uses the syntax from the LaTeX3-project, or expl3, which is meant for package and class writers. Therefore, it might seem a bit more cryptic, but it has some very nice advantages. In the code below, I've made the macro \fpeval available for use with the regular latex-syntax, for easier input, so you don't have to learn anything about the new syntax at all. Just use \fpeval{equation} like a normal LaTeX-macro.

With it, you can do lots of calculations with easier input than compared to the package fp. Especially see how much shorter and easier to read the new macros become. There are many mathematical functions available, I've added a few of them as an example at the bottom.

You can find more information about the available functions and their usage in Part XXII The l3fp package: floating points of interface3 documentation.

Note that you can always locate the documentation using the command-line/terminal by writing texdoc interface3, and that works for any package. The documentation is also available at http://ctan.org/pkg/interface3

The error

The error is due to expansions. Josephs Wright's answer here gives a great explanation and for why the original code doesn't work.

Defining your macros with xparse seems to fix this.

Edit:

I've removed the \fp_compare as fpeval has max(1,2,3) and min(1,2,3) functions available, which outputs the largest and smallest respectivily.

I also took the advice from egreg and made the macros expandable.

Code

\documentclass[twoside]{memoir}
\usepackage[
papersize={8.5in,11in},
vmargin=0.5in,
outer=1in,
inner=0.5in,
includefoot,
]{geometry}
\usepackage{xparse}

\ExplSyntaxOn  % enable LaTeX3-syntax
% If you don't want to use syntax from LaTeX3 in your code, we can make them
% available as regular LaTeX-syntax by the following commands:
\cs_set_eq:NN \fpeval \fp_eval:n % Make fpeval available. Create a Control Sequence(macro) named  \fpeval, and  Set it Equal to LaTeX3-macro \fp_eval.
\ExplSyntaxOff % disable LaTeX3-syntax

\DeclareExpandableDocumentCommand{\clamp}{ m m m }{% Make a expandable macro,
% with 3 Mandatory arguments
\fpeval{min(max(#1,#2),#3)}
}

\DeclareExpandableDocumentCommand{\unlerp}{ m m m }{% Make a expandable macro,
% with 3 Mandatory arguments
\fpeval{ ( #1 - #2 ) / ( #3 - #2 ) }%
}

\DeclareExpandableDocumentCommand{\lerp}{ m m m }{% Make a expandable macro,
%with 3 Mandatory arguments
\fpeval{ ( #3 - #2 ) * #1 + #2 }%
}

\begin{document}

\huge

\bfseries Clamp Tests
\normalfont

Clamp -5 between 0 and 100: \clamp{-5}{0}{100}

Clamp 45 between 0 and 100: \clamp{45}{0}{100}

Clamp 150 between 0 and 100: \clamp{150}{0}{100}

Clamp -0.5 between 0 and 1: \clamp{-0.5}{0}{1}

Clamp 0.5 between 0 and 1: \clamp{0.5}{0}{1}

Clamp 1.5 between 0 and 1: \clamp{1.5}{0}{1}

\bfseries Lerp Tests
\normalfont

Lerp 0 between 0 and 100: \lerp{0}{0}{100}

Lerp 0.5 between 0 and 100: \lerp{0.5}{0}{100}

Lerp 1 between 0 and 100: \lerp{1}{0}{100}

\bfseries Unlerp Tests
\normalfont

Unlerp 0 between 0 and 100: \unlerp{0}{0}{100}

Unlerp 45 between 0 and 100: \unlerp{45}{0}{100}

Unlerp 150 between 0 and 100: \unlerp{100}{0}{100}

Unlerp 0.5 between 0 and 2: \unlerp{0.5}{0}{2}

\bfseries Combination Tests
\normalfont

Store manual value in variable: \def\testa{0.5}

Use manual variable in clamp: \clamp{\testa}{0}{1}

Store unlerp result in variable: \def\testb{\unlerp{50}{0}{100}}

Output unlerp result variable: \testb\

Variable $$\rightarrow$$ clamp: \clamp{\testb}{0}{1}

\textbf{Easy calculations, just as an example:}

\verb|\fpeval{pi * 4^2 }| :
\fpeval{pi * 4^2 }

\verb|\fpeval{asind(\testb)}| :
\fpeval{asind(\testb)}

\end{document}

• "I would suggest using fp-commands from the upcoming LaTeX3-project." I'm pretty new to LaTeX. Are you saying there's a way to use LaTeX3 features from within LaTeX2e? Or are you suggesting that I move to using LaTeX3? – Joshua Walsh Oct 9 '16 at 8:25
• @YM_Industries LaTeX3 is at present not a stand-alone format but 'ideas' implemented as LaTeX2e packages. As such, you can happily intermix the two (certainly for things like expl3). – Joseph Wright Oct 9 '16 at 8:27

TeX is a macro expansion language, and so there is no such thing as 'returning' a value. For an expandable macro, one can arrange for the expansion to take place before the result is used. In some cases, for example using \ifdim, that will be part of the command itself (\ifdim performs expansion).

The calculations performed by fp are not done in an expandable way (they use assignments internally). As such, to use fp you will need to set up your calculations a different way, putting the calculation inside the same place as the comparison. As detailed in the answer by Runar Trollet, you are probably better off using an expandable calculation set up such as the one in expl3. As the other answer covers this I won't go into it further, but I would point out that you've got a lot of whitespace in your answers (see What is the use of percent signs (%) at the end of lines?). You can also make the comparison expandable:

\newcommand\clamp[3]{%
\ifdim #1pt<#2pt
#2%
\else
\ifdim #1pt>#3pt
#3%
\else
#1%
\fi
\fi
}

• Thanks for the tip, I'll be more careful about where I'm generating whitespace. – Joshua Walsh Oct 9 '16 at 8:49