4

I experienced this phenomenon in Python first, but it turned out that it is the common answer, for example MS Excel gives this. Wolfram Alpha gives an interesting schizoid answer, where it states that the rational approximation of zero is 1/5. ( 1.0 mod 0.1 )

On the other hand, if I implement the definition by hand it gives me the 'right' answer (0).

def myFmod(a,n):
    return a - floor(a/n) * n

What is going on here. Do I miss something?

starblue
  • 55,348
  • 14
  • 97
  • 151
beemtee
  • 831
  • 1
  • 8
  • 10
  • 3
    returns 0.09999999999999995 here (OS X, Python 2.5.4). The answer is dependent on your platform's C library. – Wooble Nov 18 '10 at 20:02
  • ok, sorry I did the rounding. anyway, the answer should be 0 in my understanding since 1.0 / 0.1 = 10 which is an integer number. – beemtee Nov 18 '10 at 20:05
  • Possible duplicate of [Python modulo on floats](https://stackoverflow.com/questions/14763722/python-modulo-on-floats) – user202729 Jul 27 '18 at 13:08

6 Answers6

23

Because 0.1 isn't 0.1; that value isn't representable in double precision, so it gets rounded to the nearest double-precision number, which is exactly:

0.1000000000000000055511151231257827021181583404541015625

When you call fmod, you get the remainder of division by the value listed above, which is exactly:

0.0999999999999999500399638918679556809365749359130859375

which rounds to 0.1 (or maybe 0.09999999999999995) when you print it.

In other words, fmod works perfectly, but you're not giving it the input that you think you are.


Edit: Your own implementation gives you the correct answer because it is less accurate, believe it or not. First off, note that fmod computes the remainder without any rounding error; the only source of inaccuracy is the representation error introduced by using the value 0.1. Now, let's walk through your implementation, and see how the rounding error that it incurs exactly cancels out the representation error.

Evaluate a - floor(a/n) * n one step at a time, keeping track of the exact values computed at each stage:

First we evaluate 1.0/n, where n is the closest double-precision approximation to 0.1 as shown above. The result of this division is approximately:

9.999999999999999444888487687421760603063276150363492645647081359...

Note that this value is not a representable double precision number -- so it gets rounded. To see how this rounding happens, let's look at the number in binary instead of decimal:

1001.1111111111111111111111111111111111111111111111111 10110000000...

The space indicates where the rounding to double precision occurs. Since the part after the round point is larger than the exact half-way point, this value rounds up to exactly 10.

floor(10.0) is, predictably, 10.0. So all that's left is to compute 1.0 - 10.0*0.1.

In binary, the exact value of 10.0 * 0.1 is:

1.0000000000000000000000000000000000000000000000000000 0100

again, this value is not representable as a double, and so is rounded at the position indicated by a space. This time it rounds down to exactly 1.0, and so the final computation is 1.0 - 1.0, which is of course 0.0.

Your implementation contains two rounding errors, which happen to exactly cancel out the representation error of the value 0.1 in this case. fmod, by contrast, is always exact (at least on platforms with a good numerics library), and exposes the representation error of 0.1.

Stephen Canon
  • 103,815
  • 19
  • 183
  • 269
  • This doesn't explain why `1.0 - floor(1.0/0.1) * 0.1` gives zero though. – kennytm Nov 18 '10 at 20:27
  • yeah, I get it. 1.0 can be exactly represented by a double while 0.1 can't. Since the closest representable number is greater than the original the result of the division will be something like 9.99999999999, which becomes 9 after truncation. – beemtee Nov 18 '10 at 20:36
  • @KennyTM: added an answer to the follow-up question for you. – Stephen Canon Nov 18 '10 at 22:00
1

This result is due to machine floating point representation. In your method, you are 'casting' (kinda) the float to an int and do not have this issue. The 'best' way to avoid such issues (esp for mod) is to multiply by a sufficiently large enough int (only 10 is needed in your case) and perform the operation again.

fmod(1.0,0.1)

fmod(10.0,1.0) = 0

g19fanatic
  • 10,567
  • 6
  • 33
  • 63
  • where am I doing the "kinda 'casting'" ? if 'my' method gives the right answer why do others use other methods? – beemtee Nov 18 '10 at 20:14
  • there isn't exactly 'casting' in python like there is in c++ (hence the kinda). You are effectively making the floating point become an int when you `floor` your number. Stephen Canon has a more detailed answer that shows you that it [i]does[/i] give the correct answer just not the one you were expecting... – g19fanatic Nov 18 '10 at 20:19
1

From man fmod:

The fmod() function computes the floating-point remainder of dividing x by y. The return value is x - n * y, where n is the quotient of x / y, rounded towards zero to an integer.

So what happens is:

  1. In fmod(1.0, 0.1), the 0.1 is actually slightly larger than 0.1 because the value cannot be exactly represented as a float.
  2. So n = x / y = 1.0 / 0.1000something = 9.9999something
  3. When rounded towards 0, n actually becomes 9
  4. x - n * y = 1.0 - 9 * 0.1 = 0.1

Edit: As for why it works with floor(x/y), as far as I can tell this seems to be an FPU quirk. On x86, fmod uses the fprem instruction, whereas x/y will use fdiv. Curiously 1.0/0.1 seems to return exactly 10.0:

>>> struct.pack('d', 1.0/0.1) == struct.pack('d', 10.0)
True

I suppose fdiv uses a more precise algorithm than fprem. Some discussion can be found here: http://www.rapideuphoria.com/cgi-bin/esearch.exu?thread=1&fromMonth=A&fromYear=8&toMonth=C&toYear=8&keywords=%22Remainder%22

adw
  • 4,901
  • 1
  • 25
  • 18
  • Thanks! This is the clear and definitive answer. The only question left is why the hand defined function doesn't suffer from this problem? – beemtee Nov 18 '10 at 20:40
  • No, it's not an FPU quirk, but rather a consequence of the fact that the hand-written algorithm involves rounding, whereas the `fmod` function always delivers an exact answer. See my answer for more details. Note also that the particular implementation of `fmod` is specific to your platform's math library, not to the hardware (although many x86 math libraries use the `fprem` instruction, not all do). – Stephen Canon Nov 18 '10 at 22:14
  • @Stephen Canon: You're right, of course. I somehow missed that the extra `fdiv` step causes yet more rounding. – adw Nov 18 '10 at 22:49
  • it took me a few minutes to see, too. – Stephen Canon Nov 18 '10 at 22:53
  • @StephenCanon: I wonder how often knowing the difference between a number and the exact next lower multiple of a divisor is more useful than knowing the difference between a number and the closest representable number to the next lower multiple? – supercat Dec 19 '13 at 21:14
0

fmod returns x-i*y, which is less than y, and i is an integer. 0.09.... is because of floating point precision. try fmod(0.3, 0.1) -> 0.09... but fmod(0.4, 0.1) -> 0.0 because 0.3 is 0.2999999... as a float.

fmod(1/(2.**n), 1/(2.**m) will never produce anything but 0.0 for integer n>=m.

khachik
  • 28,112
  • 9
  • 59
  • 94
0

This gives the right answer:

a = 1.0
b = 0.1

a1,a2 = a.as_integer_ratio()
b1,b2 = b.as_integer_ratio()
div = float(a1*b2) / float(a2*b1)
mod = a - b*div
print mod
# 0.0

I think it works because by it uses rational equivalents of the two floating point numbers which provides a more accurate answer.

martineau
  • 119,623
  • 25
  • 170
  • 301
0

The Python divmod function is instructive here. It tells you both the quotient and remainder of a division operation.

$ python
>>> 0.1
0.10000000000000001
>>> divmod(1.0, 0.1)
(9.0, 0.09999999999999995)

When you type 0.1, the computer can't represent that exact value in binary floating-point arithmetic, so it chooses the closest number that it can represent, 0.10000000000000001. Then when you perform the division operation, floating-point arithmetic decides that the quotient has to be 9, since 0.10000000000000001 * 10 is larger than 1.0. This leaves you with a remainder that is slightly less than 0.1.

I would want to use the new Python fractions module to get exact answers.

>>> from fractions import Fraction
>>> Fraction(1, 1) % Fraction(1, 10)
Fraction(0, 1)

IOW, (1/1) mod (1/10) = (0/1), which is equivalent to 1 mod 0.1 = 0.

Another option is to implement the modulus operator yourself, allowing you to specify your own policy.

>>> x = 1.0
>>> y = 0.1
>>> x / y - math.floor(x / y)
0.0
Shane Hathaway
  • 2,141
  • 1
  • 13
  • 9
  • BTW, Wolfram Alpha gets this answer too, if you ask it in terms of exact fractions instead of floating-point numbers: http://www.wolframalpha.com/input/?i=1+mod+1%2F10 – Shane Hathaway Nov 18 '10 at 22:09