4

When I did (0.0006*100000)%10 and (0.0003*100000)%10 in python it returned 9.999999999999993 respectively, but actually it has to be 0. Similarly in c++ fmod(0.0003*100000,10) gives the value as 10. Can someone help me out where i'm getting wrong.

Arnaud Denoyelle
  • 29,980
  • 16
  • 92
  • 148
lijo050
  • 233
  • 4
  • 14
  • 3
    that's just how floating point number works. You will *NEVER* get perfect accuracy with them, because all float values in digital systems are approximations. – Marc B Jul 23 '14 at 15:14
  • 3
    @MarcB, you can get perfect accuracy. Just not all numbers – tangrs Jul 23 '14 at 15:14
  • @MarcB - Yes but there is a huge difference between 9.999 and 0. OP- why not post the code so we can take a look at it ourselves? – noobuntu Jul 23 '14 at 15:15
  • @tangrs Then you don't get perfect accuracy, if some numbers are missing. – Depado Jul 23 '14 at 15:15
  • @Depado The number 1.0, for example, can be accurately and perfectly represented in a float. – tangrs Jul 23 '14 at 15:17
  • 4
    @Depado You get an exact result, always. It may not be the result you want, and it is often different than the result would be if you did the same arithmetic over the reals, but it is exact and fully deterministic. Machine floating point are not real numbers, and obey different rules. – James Kanze Jul 23 '14 at 15:17
  • possible duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – tmyklebu Jul 23 '14 at 15:19
  • 2
    Also, am I the only one who gets iffy around modulo arithmetic with floating point numbers? – tangrs Jul 23 '14 at 15:19
  • @tangrs then you don't need anymore numbers for this one. So you're not missing numbers. The only exception I know is 0.999... = 1. Which is mathematically correct. – Depado Jul 23 '14 at 15:19
  • @tangrs: Nothing weird going on with it, as JamesKanze pointed out. – tmyklebu Jul 23 '14 at 15:20
  • but, for all other values like (0.0002*100000)%10 and (0.0005*100000)%10 .. i'm getting the perfect answer 0. – lijo050 Jul 23 '14 at 15:20
  • 0.0006 * 100000 -> 59.9999999999993, 0.0003 * 100000 -> 29.99999999996, basically – Marc B Jul 23 '14 at 15:21
  • @user141918 Within the precision of the floating point arithmetic. – James Kanze Jul 23 '14 at 15:21
  • @user141918 0.0002 and 0.0005 are both numbers that are able to be represented in a base-2 floating point number losslessly while 0.0006 and 0.0003 are not. – tangrs Jul 23 '14 at 15:21
  • 2
    @user141918: because some numbers CAN be represented perfectly in floats, but it's easier to just assume that NONE can and treat all float operations as approximations. – Marc B Jul 23 '14 at 15:21
  • http://floating-point-gui.de – n. m. could be an AI Jul 23 '14 at 15:24
  • 4
    @tangrs Neither 0.0002 nor 0.0005 can be exactly represented. The both come in slightly high: 0.00020000000000000000958434720477185919662588275969028472900390625 and 0.0005000000000000000104083408558608425664715468883514404296875 – Patricia Shanahan Jul 23 '14 at 15:27
  • 1
    @PatriciaShanahan Hmmm, I may have to check on my maths then... – tangrs Jul 23 '14 at 15:28
  • @tangrs 0.0002 is 2/10000, or in its lowest terms 1/5000. There is no integer power of two that is a multiple of 5000, so there is no way to express that exactly as a binary fraction. – Patricia Shanahan Jul 23 '14 at 15:31

3 Answers3

10

The closest IEEE 754 64-bit binary number to 0.0003 is 0.0002999999999999999737189393389513725196593441069126129150390625. The closest representable number to the result of multiplying it by 100000 is 29.999999999999996447286321199499070644378662109375.

There are a number of operations, such as floor and mod, that can make very low significance differences very visible. You need to be careful using them in connection with floating point numbers - remember that, in many cases, you have a very, very close approximation to the infinite precision value, not the infinite precision value itself. The actual value can be slightly high or, as in this case, slightly low.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
5

Just to give the obvious answer: 0.0006 and 0.0003 are not representable in a machine double (at least on modern machines). So you didn't actually multiply by those values, but by some value very close. Slightly more, or slightly less, depending on how the compiler rounded them.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
0

May I suggest using the remainder function in C?

It will compute the remainder after rounding the quotient to nearest integer, with exact computation (no rounding error):

remainder = dividend - round(dividend/divisor)*divisor

This way, your result will be in [-divisor/2,+divisor/2] interval.
This will still emphasize the fact that you don't get a float exactly equal to 6/10,000 , but maybe in a less surprising way when you expect a null remainder:

remainder(0.0006*100000,10.0) -> -7.105427357601002e-15
remainder(0.0003*100000,10.0) -> -3.552713678800501e-15

I don't know of such remainder function support in python, but there seems to be a match in gnulib-python module (to be verified...)
https://github.com/ghostmansd/gnulib-python/blob/master/modules/remainder

EDIT Why does it apparently work with every other N/10,000 in [1,9] interval but 3 and 6?

It's not completely lucky, this is somehow good properties of IEEE 754 in default rounding mode (round to nearest, tie to even).

The result of a floating point operation is rounded to nearest floating point value.
Instead of N/D you thus get (N/D+err) where the absolute error err is given by this snippet (I'm more comfortable in Smalltalk, but I'm sure you will find equivalent in Python):

| d |
d := 10000.
^(1 to: 9) collect: [:n | ((n/d) asFloat asFraction - (n/d)) asFloat]

It gives you something like:

#(4.79217360238593e-21 9.58434720477186e-21 -2.6281060661048628e-20 1.916869440954372e-20 1.0408340855860843e-20 -5.2562121322097256e-20 -7.11236625150491e-21 3.833738881908744e-20 -2.4633073358870662e-20)

Changing the last bit of a floating point significand leads to a small difference named the unit of least precision (ulp), and it might be good to express the error in term of ulp:

| d |
d := 10000.
^(1 to: 9) collect: [:n | ((n/d) asFloat asFraction - (n/d)) / (n/d) asFloat ulp]

the number of ulp off the exact fraction is thus:

#(0.3536 0.3536 -0.4848 0.3536 0.096 -0.4848 -0.0656 0.3536 -0.2272)

The error is the same for N=1,2,4,8 because they are essentially the same floating point - same significand, just the exponent changes.
It's also the same for N=3 and 6 for same reason, but very near the maximum error for a single operation which is 0.5 ulp (unluckily the number can be half way between two floats).
For N=9, the relative error is smaller than for N=1, and for 5 and 7, the error is very small.

Now when we multiply these approximation by 10000 which is exactly representable as a float, (N/D+err)D is N+Derr, and it's then rounded to nearest float. If D*err is less than half distance to next float, then this is rounded to N and the rounding error vanishes.

| d |
d := 10000.
^(1 to: 9) collect: [:n | ((n/d) asFloat asFraction - (n/d)) * d / n asFloat ulp]

OK, we were unlucky for N=3 and 6, the already high rounding error magnitude has become greater than 0.5 ulp:

#(0.2158203125 0.2158203125 -0.591796875 0.2158203125 0.1171875 -0.591796875 -0.080078125 0.2158203125 -0.138671875)

Beware, the distance is not symmetric for exact powers of two, the next float after 1.0 is 1.0+2^-52, but before 1.0 it's 1.0-2^-53.

Nonetheless, what we see here, is that after the second rounding operation, the error did annihilate in four cases, and did cumulate only in a single case (counting only the cases with different significands).

We can generalize that result. As long as we do not sum numbers with very different exponents, but just use muliply/divide operations, while the error bound can be high after P operations, the statistical distribution of cumulated errors has a remarkably narrow peak compared to this bound, and the result are somehow surprisingly good w.r.t. what we regularly read about float imprecision. See my answer to The number of correct decimal digits in a product of doubles with a large number of terms for example.

I just wanted to mention that yes, float are inexact, but they sometimes do such a decent job, that they are fostering the illusion of exactness. Finding a few outliers like mentionned in this post is then surprising. The sooner surprise, the least surprise. Ah, if only float were implemented less carefully, there would be less questions in this category...

Community
  • 1
  • 1
aka.nice
  • 9,100
  • 1
  • 28
  • 40