3

I'm having a problem with modulus on a floating point number in Python. This code:

...
print '(' + repr(olddir) + ' + ' + repr(self.colsize) + ') % (math.pi*2) = ' + repr((olddir+self.colsize)
...

Prints:

(6.281876310240881 + 0.001308996938995747) % (math.pi*2) = 2.9043434324194095e-13

I know floating point numbers aren't precise. But I can't get this to make any sense.

I don't know if it is in any way related but Google Calculator can't handle this calculation either. This is the output from Google Calculator:

(6.28187631024 + 0.001308996939) % (pi * 2) = 6.28318531

What is causing this calculation error? And how can I avoid it in my Python program?

paldepind
  • 4,680
  • 3
  • 31
  • 36
  • Now that you've edited the values, it's hard to see what the calculation error is supposed to be. What result do you expect? – Mark Ransom May 13 '11 at 21:10
  • @Mark: Look at the OP's comments to my answer to understand what the actual problem is. – Sven Marnach May 13 '11 at 21:12
  • 1
    @Sven, thanks - I've known scientific notation for so long now that it's invisible. I forget how confusing it was at first sight. – Mark Ransom May 13 '11 at 21:20

2 Answers2

4

Using str() to print a floating point number actually prints a rounded version of the number:

>>> print repr(math.pi)
3.1415926535897931
>>> print str(math.pi)
3.14159265359

So we can't really reproduce your results, since we don't know the exact values you are doing the computation with. Obviously, the exact value of olddir+self.colsize is slightly greater than 2*math.pi, while the sum of the rounded values you used in Google Calculator is slightly less than 2*math.pi.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • I've updated my post using `repr()` instead of `str()`. It should be reproduceable now although I can't see why it matters. – paldepind May 13 '11 at 20:52
  • 1
    @paldepind: Now try in [Google with the new values](http://www.google.com/search?q=%286.281876310240881+%2B+0.001308996938995747%29+%25+%28pi%2a2%29) to see why it matters – Sven Marnach May 13 '11 at 20:58
  • Yes, it matches Google Calculator now. But this calculation is still wrong. It should return something close to zero. – paldepind May 13 '11 at 21:07
  • 1
    @paldepind: Have a look at the [Wikipedia explanation of this notation](http://en.wikipedia.org/wiki/Scientific_notation#E_notation). – Sven Marnach May 13 '11 at 21:26
  • 1
    @paldepind, no you're not stupid - just inexperienced. No shame in that. – Mark Ransom May 13 '11 at 21:44
2

The difference between str and repr

>>> import scipy
>>> pi = scipy.pi
>>> str(pi)
'3.14159265359'
>>> repr(pi)
'3.1415926535897931'

str truncates floating point numbers to 12 digits, where repr gives the internal representation (as a string).

EDIT: So in summary, the problem arose because you rounded prematurely and are calculating the modulus of something via a number that's very close to it. With floating point numbers, rounding is inevitably involved in converting decimal numbers into binary.

First, do an example of how rounding hurts you with actual math (not floating point math). Look at (3.14+3.14) % (3.14+3.14), which is obviously zero. Now what would happen if we rounded the digits to one decimal digit first on one side? Well (3.1+3.1) % (3.14+3.14) = 6.2 % (6.28) = 6.2 (what google gave you). Or if you did round(3.14159,5) + round(3.14159,5) % (3.14159 + 3.14159) = 6.2832 % 6.28318 = 2e-5.

So in by rounding to N digits (by using str which effectively rounds the numbers), your calculation is only accurate to less than N digits. To have this work going forward force rounding at some higher digit (keeping two calculated digits for safety) is necessary. E.g., str rounds at digit 12, so maybe we should round at digit 10.

>>> round(6.28187631024 + 0.001308996939,10) % (round(pi * 2,10))
0
dr jimbob
  • 17,259
  • 7
  • 59
  • 81