7.0pt ≠ x⋅10.0pt for all x

Consider the following input:

\documentclass{article}
\begin{document}
\newlength{\smallertopskip}
\setlength{\smallertopskip}{.700004577636718749999999999999999999999999999999\topskip}
\newlength{\largertopskip}
\setlength{\largertopskip}{.700004577636718750000000000000000000000000000000\topskip}

Topskip: \the\topskip

Smaller: \the\smallertopskip

Larger: \the\largertopskip
\end{document}


Running pdflatex on it results in

Topskip: 10.0pt

Smaller: 6.99997pt

Larger: 7.00012pt

As you see above, I tried hard to get exactly 7pt as a result of multiplication of some fixed-point constant with \topskip, but failed. Sure, it's very well-known that fixed-point computations are really inaccurate in TeX, but I'm wondering whether some external package could provide us with a rather general-purpose multiplication function (say, \mult) that is more precise than the built-in multiplication such that

\newlength{\myLength}
\setlength{\myLength}{\mult{x}{\topskip}}
\the\myLength


(or similar code) would result in 7.0pt for some verbatim fixed-point constant x assuming that \topskip is 10.0pt? Notice that both 7.0 and 10.0 are representable in binary, and the mathematically correct answer x=7/10=0.7 is not representable in binary, but the fixed-point arithmetics is different anyway...

Aside: As some answers and comments notice, the difference between the under- and overapproximation is invisible to the naked eye. But the difference has an effect on potential further computations with \myLength, including choosing, e.g., a font size (which is a non-continuous operation).

• To achieve exact results on any system, TeX does not use floating-point at all for lengths. It does everything with integer units of sp, where 1 pt equals 65536 sp. (Another way of saying it is that it uses fixed-point arithmetic.) Your 10 pt corresponds to 655360 sp, your \smallertopskip to 458750 sp, and your \largertopskip to 458760 sp. (And your desired “target” 7 pt corresponds to 458752 sp.) Knuth regrets using binary instead of decimal here, probably because it leads to confusion like this. – ShreevatsaR Feb 7 '19 at 4:55
• You wrote, "Sure, it's very well-known that floating-point computations are really inaccurate in TeX." Actually, TeX does not use floating-point arithmetic, for reasons that have been noted many years ago -- by Knuth himself, as well as by others. Instead, as @ShreevatsaR has already point out in a comment, TeX uses fixed-point arithmetic. A separate comment: Are you concerned that there might be a meaningful, i.e., visually observable, difference between 6.9997pt and 7.00012pt? Please clarify. – Mico Feb 7 '19 at 5:39
• (Since I realized an ambiguity in my first comment) Using decimal instead of binary won't change the mathematics (with bounded memory you can have only limited precision), but just makes the human experience easier: e.g. instead of having fractions that are multiples of 1/65536 if you had say 1/10000 or 1/100000, then instead of 0.70000457763671875 being an interesting bound (it's halfway between two representable numbers 45875/65536 = 0.6999969482421875 and 45876/65536 = 0.70001220703125), some consecutive representable numbers may be (say) 0.69999, 0.70000, 0.70001 -- easier to understand. – ShreevatsaR Feb 7 '19 at 18:38

It's not really that TeX floating point calculations are inaccurate, it simply isn't doing floating point at all, so you either need a macro implementation such as the xfp package in Werner's answer, or suitably scale the calculation so that it is accurate in the range of fixed point arithmetic being used by TeX and using integer quantities that can be stored exactly (as opposed to 0.7) also helps.

\documentclass{article}

\begin{document}
\newlength{\smallertopskip}
\setlength{\smallertopskip}{\dimexpr 7\topskip/10\relax}

Topskip: \the\topskip

Smaller: \the\smallertopskip

\end{document}


Although it only really makes a difference in this special case that the value is a known integer. In general, if you are going to need decimal places in the answer just rounding to 1 or two decimal places will ensure a consistent result and not make any real difference to the output, .000001pt isn't very big.

• That's probably better than loading thousands of lines of expl3 macros. – Henri Menke Feb 7 '19 at 8:07
• @HenriMenke yes but if you know you want a zero decimal places answer it's easy to arrange an integer answer, if you want 75% of 11pt, then perhaps less so, it depends why you are worrying about a +/-.00001 pt difference I suppose. – David Carlisle Feb 7 '19 at 11:42
• @user0 you need an awful lot of rounding to make that visible, it really isn't ever going to happen. How many further calculations are you ever going to do with topskip? and dimexpr and gluexpr are the same here as the form <factor><glue> discards the stretch and shrink components anyway. – David Carlisle Feb 7 '19 at 20:11
• but given that you can not store .7 exactly in binary, you really can't worry about a difference in the 5th decimal place or you'd never be able to do any calculations at all. @user0 – David Carlisle Feb 7 '19 at 20:24
• @user0 yes that's why I constructed the arithmetic as I did in this answer, keeping all intermediate steps exactly representable *7 divide 10 rather than multiply by 0.7 – David Carlisle Feb 7 '19 at 20:48

Use xfp:

7.0pt

\documentclass{article}

\usepackage{xfp}

\begin{document}

\newlength{\myLength}%
\setlength{\myLength}{\fpeval{round(0.7 * \topskip, 0)}pt}%
\the\myLength

\end{document}


Dimensions in \fpeval are converted to pt and stripped of the dimension part in order to perform calculations (hence the addition of a "closing pt").

If you want you can define

\newcommand{\mult}[2]{\fpeval{round(#1 * #2, 0)}pt}


\setlength{\myLength}{\mult{0.7}{\topskip}}

• @user0: It may counter the errors that could result from floating point computations. – Werner Feb 7 '19 at 5:28
• @user0 Of course, even with xfp (or any approach) once you assign to a length, you'll only get integer multiples of 1 sp (= 1/65536 pt), so this only affects the rounding that happens to 0.7 (or whatever) before the multiplication; it doesn't affect the range of possible values for the length at the end. – ShreevatsaR Feb 7 '19 at 6:25
• This looks like a quite complicated way to say \setlength{\mylenth}{7pt}. – Ulrike Fischer Feb 7 '19 at 7:50
• @user0 that was a joke. But I would use \setlength{\myLength}{\fpeval{\dim_to_decimal:n {\topskip} * 0.7}pt} instead of rounding the result. – Ulrike Fischer Feb 7 '19 at 19:12

There is no way to get a length of 458752sp from <factor>\topskip if \topskip has the value 10pt, that is, 655360sp, because TeX don't do floating-point computations, but fixed-point base two arithmetic.1

The binary representation of 7/10 is 0.10(1100), parentheses denote the period. and the multiplication rules of TeX can only provide either 458750sp or 458760sp, represented respectively as 6.99997pt and 7.00012pt.

The difference between the upper and lower best representations is 10sp, which is less than 0.000435 millimeters or 0.00016 points.

Since the usual value of \vfuzz is 0.1pt (less than 0.03mm), there should be no concern about getting an “exact” value: you'd need to cumulate more than 680 such errors in order to exceed the \vfuzz.

\documentclass{article}

\newlength{\multipletopskip}

\begin{document}

Topskip: \the\topskip (\number\topskip sp)

70\% topskip: \the\dimexpr 7\topskip/10\relax (\number\dimexpr 7\topskip/10\relax sp)

\setlength{\multipletopskip}{0.7\topskip}
0.7 topskip: \the\multipletopskip (\number\multipletopskip sp)

\setlength{\multipletopskip}{0.70001\topskip}
0.70001 topskip: \the\multipletopskip (\number\multipletopskip sp)

\end{document}


As you see, 0.7\topskip is accurate up to 2sp, less than 0.00009mm.2

Unless you completely override TeX's computation by using a different model such as IEEE754 (decimal32) as is done in Werner's answer, you can't get “exact” values.

Footnotes

1 When TeX was written, there was no agreed upon standard for floating-point computations and Knuth's aim was to obtain the same output on every machine TeX was implemented on. Using 64 bits instead of 32 could have achieved “better” accuracy, but at the expense of speed and need for memory: PC's of that time might have even less than 640 kiB of RAM.

2 Being a skip, it would be more sensible to use \glueexpr rather than \dimexpr, as noted by GuM in comments. Note that <factor><skip register> will discard the plus and minus components, whereas \multiply and \divide don't. So

\setlength{\multipletopskip}{\glueexpr\topskip*7/10\relax}


could be better.

• In fact, how tiny these differences are becomes clearer when we switch to small units: a difference of 10 sp is about 53 nanometres. As The TeXbook says “Since the wavelength of visible light is approximately 100 sp, [DEK adds comment in texbook.tex: in fact, violet=75sp, red=135sp] rounding errors of a few sp make no difference to the eye”. – ShreevatsaR Feb 7 '19 at 12:49
• Don't you mean 0.1pt is approx. 0.03mm, not 0.3mm? 0.3mm is about 1/3 of a mm, which is about 1/75 of an inch, which is about 1pt not 0.1pt. – alephzero Feb 7 '19 at 16:09
• This answer contains the implicit remark that eTeX’s \dimexpr (or \glueexpr) provides better precision: why not to make this remark explicit? – GuM Feb 7 '19 at 18:59
• @user0: I don’t think it is a good idea to allow the \topskip glue to stretch or shrink; nonetheless, Knuth decided to make \topskip a <glue parameter>, not a <dimen parameter>, so, in the principle, using \glueexpr should be safer (and more elegant). It depends, however, on what you are trying to achieve: you may well want to kill \tpskip’s shrinkability/stretchability intentionally. – GuM Feb 7 '19 at 19:15
• @user0 7\topskip discards the stretch, if you don't want to do that use \setlength{\myLengthG}{\glueexpr\topskip*7/10\relax} – David Carlisle Feb 7 '19 at 20:15

This should not be an answer, but rather a comment both to @egreg’s answer and to David Carlisle’s; unfortunately, I need to include some sample code, and this can only be done (in an intelligible form) in an answer. I willingly concede that it doesn’t add anything substantial to those two answers—except, perhaps, a bit of clarity. I’m ready to remove this answer if either of the abovementioned authors clarifies his.

As already repeatedly remarked, Knuth’s TeX does (or rather, did) its calculations with dimensions using 32-bit fixed points arithmetics; all modern typesetting engines, however, incorporate the so-called “e-TeX” (for Extended, or Enhanced, TeX) extensions, among which is the ability to perform dimension scaling, that is, multiplication of a dimension for a fraction, with 64-bit precision. More precisely, e-TeX extensions introduce a new type of syntax by means of which dimensions can be specified, that enables the use of “expressions”, in the customary sense of the term; in particular, you are allowed to multiply a certain dimension for a fraction, as in

\setlength\someotherdimen{\dimexpr \somedimen * 7/10}


(the primitive \dimexpr marks the beginning of a “dimen-valued” expression; the end is implicitly marked by the first token that cannot be interpreted as part of the expression itself—you can assume it is the closing brace, in the above example). When this is done, a 64-bit temporary register is used to hold the intermediate result of the multiplication of the dimension for the numerator of the fraction (\somedimen * 7, in the above example); thank to this expedient, when the subsequent division for the denominator (10, in our case) is performed, all bits of the final (32-bit) result are significant.

As already said, \dimexpr starts a “dimen-valued” expression; there exists a similar primitive for “glue-valued” expressions, named \glueexpr. Note, however, the difference, that others have already explained, between

\setlength\someotherskip{\glueexpr 7\someskip /10}


and

\setlength\someotherskip{\glueexpr \someskip * 7/10}


In the former, 7\someskip is converted to a dimen value, destroying (that is, zeroing) its stretch and shrink components, if any; this doesn’t happen with the latter.

The following code proves that

\setlength{\someotherskip}{\glueexpr \topskip * 7/10}


attains the same precision as explicitly setting \someotherskip to 7pt (and similarly for the stretch and shrink components, if present):

% My standard header for TeX.SX answers:
\documentclass[a4paper]{article} % To avoid confusion, let us explicitly
% declare the paper format.

\usepackage[T1]{fontenc}         % Not always necessary, but recommended.
% End of standard header.  What follows pertains to the problem at hand.

\newcommand*{\ReportDimen}[2]{%
#1:\>\texttt{\the #2}\\\>\texttt{(\number #2sp)}\\%
}
\newcommand*{\ReportGlue}[2]{%
#1:\>\texttt{\the #2}\\\>%
\texttt{(%
\number #2sp
plus  \number\expandafter\dimexpr\the\gluestretch #2\relax sp
minus \number\expandafter\dimexpr\the\glueshrink  #2\relax sp%
)}\\%
}

\setlength{\topskip}{10.0pt plus 1pt minus 1 pt}
\newlength{\smallertopskip}
\setlength{\smallertopskip}{.700004577636718749999999999999999999999999999999\topskip}
\newlength{\largertopskip}
\setlength{\largertopskip}{.700004577636718750000000000000000000000000000000\topskip}
\newlength{\eTeXTopskip}
\setlength{\eTeXTopskip}{\glueexpr \topskip * 7/10}
\newlength{\sevenpoints}
\setlength{\sevenpoints}{7.0pt plus .7pt minus .7pt}

\begin{document}

\begin{tabbing}

• Why the \expandafter after \number and before \dimexpr in \ReportGlue? Those shouldn't be necessary, as well as \the in that expression and the \dimexpr. \number\gluestretch #2sp should suffice. – Skillmon likes topanswers.xyz Apr 1 '19 at 22:00