12

In the manual on page 902, it says that

Normally, when a list item ... is encountered, there should already have been two list items before it, which where numbers.

Examples of numbers are 1, -10, or -0.24. Let us call these numbers x and y and let d:=y−x be their difference. Next, there should also be one number following the three dots, let us call this number z.

In this situation, the part of the list reading x,y,...,z is replaced by x, x+d, x+2d, x+3d,...,x+md where the last dots are semantic dots, not syntactic dots. The value m is the largest number such that x+md≤z if d is positive or such that x+md ≥ z if d is negative.

Perhaps it is best to explain this by some examples: The following < list> have the same effects:

\foreach \x in {0,0.1,...,0.5} {\x, } yields 0, 0.1, 0.20001, 0.30002, 0.40002,

As here x=0, we deduce that 2d=0.20001 and therefore only d=0.100005.

Edit: 3d=0.30002 and therefore d=0.10000666666...

Edit: Then, in this case, 0 is subtracted from a 0.1 entry and this 0.1 entry is modified. How is this possible since nothing is subtracted from 0.1 at all? Does this mean that subtracting 0 subtracts something from the number 0.1?

  • Is it due to binary subtraction in base 2 with TeX? with pgffor?
  • is it because of the binary coding of the number 0 in TeX? with pgffor?
  • How does pgffor calculate the increment in a foreach loop?

Translated with www.DeepL.com/Translator

10
  • 2
    very few computer programming languages can store 0.1 exactly so such roundoff errors are not unexpected, and tex arithmetic is not particularly acurate anyway, using fixed precision dimen calculations. Commented Aug 14, 2018 at 13:02
  • @DavidCarlisle If I understand correctly, the problem therefore comes from coding the decimal part of the real numbers and not the subtraction.
    – AndréC
    Commented Aug 14, 2018 at 13:30
  • 2
    You cannot deduce from 2d=0.20001 that d=0.100005. If you do \dimen0= 0.100005pt \showthe\dimen0, you get 0.1pt. If you add \advance\dimen0 by 0.1pt \showthe\dimen0 you get 0.20001pt. PGF uses dimen registers, which are bound to the conversions to and from binary arithmetic.
    – egreg
    Commented Aug 14, 2018 at 16:25
  • @egreg Indeed, I know that there is an approximation because 2d=0.20001 and 3d=0.30002. It remains to be seen why not subtract nothing (subtract 0) subtracts something!
    – AndréC
    Commented Aug 14, 2018 at 16:39
  • @AndréC I can see no subtraction. Where do you see it?
    – egreg
    Commented Aug 14, 2018 at 16:40

2 Answers 2

6

How does \foreach \x in { a, b, ..., z } { <code> } works?

Basically, PGF does

\start=a pt
\step=b pt
\advance\step by -\start

Here a, b and z should be decimal numbers, \start and \step are dimen registers (not the real names). Then, as the manual explains, the <code> is executed with \start having the initial value; then \advance\start by \step is executed and <code> follows. The loop goes on until \start>z pt (when the <code> will not be executed). This for \step>0pt, but the case \step<0pt is similar.

Now, how does arithmetic with dimen registers work in TeX?

A dimen register value is shown in points, but actually stored in “scaled points”, where one point is 65536 scaled points.

In your case, \step is set to 0.1pt, which corresponds to 6554sp. Adding again 0.1pt makes the internal value to be 13108sp. When 13108sp is converted back to points, the value is 0.20001pt.

However, \dimen0=0.2pt corresponds to an internal value of 13107sp, because 65536 multiplied by 2 is 131072. And 0.00001pt is 1sp, the least nonzero TeX dimension.

With further additions, rounding errors accumulate.

Using \foreach with decimal values should be avoided when possible; when the values are integer, no rounding takes place.

8
  • I thought the default unit for TikZ was cm instead of pt, wasn’t it? So 0.1cm is represented as 186479sp, which results (0.1+0.1)cm as 372958sp and so on. While 0.2cm is supposed to be 372930sp. Commented Aug 14, 2018 at 17:30
  • 1
    @RuixiZhang This has nothing to do with the default unit; it is how arithmetic with decimal numbers (not dimensions) is implemented.
    – egreg
    Commented Aug 14, 2018 at 17:44
  • @egreg if \foreach loops with decimal values should be avoided, is it better to use \multido or how else?
    – AndréC
    Commented Aug 14, 2018 at 17:52
  • 1
    Thank you for the explanation! I was under the impression that drawing at (0.1,0) implies (0.1cm,0cm) being the actual implementation. How embarrassing! I’m glad I learn something new today. Commented Aug 14, 2018 at 17:54
  • @AndréC I do believe my second and third \foreach’s hint possible workarounds. Commented Aug 14, 2018 at 17:55
12

0.1 in binary has infinite representation. See, for instance, Why 0.1 Does Not Exist In Floating-Point? So there will be rounding errors. Also, as @DavidCarlisle pointed out in the comment, TeX is good at integer arithmetic, which adds another layer of error.

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}[scale=16]
\foreach \x in {0,0.1,...,0.5}
    \node at (\x,0.1) {\x};
\draw (0,0.05) -- (0.5,0.05);
\foreach \x[evaluate=\x as \y using \x/10] in {0,1,...,5}
    \node at (\y,0) {\y};
\draw (0,-0.05) -- (0.5,-0.05);
\foreach \x[evaluate=\x as \y using \x/10] in {0,1,...,5}
    \node at (\y,-0.1) {\pgfmathprintnumber[fixed,precision=1]{\y}};
\end{tikzpicture}
\end{document}

rounding errors

Added: The binary representation of 0.1 is

0.00011001100110011... (0011 on repeat)

If the computer stores the above using 16 binary decimals, then it will become

0.0001100110011010

which converts back to 3277/32768 = 0.100006103515625. Then you would end up with 0.20001220703125, 0.300018310546875 and 0.4000244140625. There is no subtraction involved here. This is just how computer stores numbers.

Second edit: As @egreg pointed out, the actual “floating-point arithmetic” in TeX is implemented via dimension register. So you would then be subjected to the rounding error that comes with how pt is converted to sp.

Coincidentally, since 3277/32768 = 6554/65536, this matches perfectly with my illustrated conversion above: 0.1 through 0.5 are being accumulated precisely as 0.100006103515625, 0.20001220703125, 0.300018310546875, 0.4000244140625, and 0.500030517578125.

2
  • Certainly 0.1 has an infinite binary writing, but here we subtract nothing from it at all since we subtract 0. Then why does the result of the subtraction 0.1-0 modify the writing of 0.1 in binary? I edited the question.
    – AndréC
    Commented Aug 14, 2018 at 15:58
  • @AndréC I don’t fully understand why you keep mentioning “subtraction”. I added some binary conversion to illustration where rounding errors came from. Commented Aug 14, 2018 at 16:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .