How to avoid math.sin(math.pi*2*VERY LARGE NUMBER) having a much larger error margin than math.sin(math.pi*2)?

Viewed 188

I've read in other questions that that for example sin(2π) is not zero due to floating point representation, but is very close. This very small error is no issue in my code as I can just round up 5 decimals for example.

However when multiplying 2π with a very large number, the error is magnified a lot. The answer should be zero (or close), but is far from it.

Am I doing something fundamentally wrong in my thinking? If not, how can I avoid the error margin of floating numbers for π to get "magnified" as the number of periods (2*PI*X) → ∞ ?

Notice that all the 3 last results are the same. Can anyone explain why this is even though 5) is exactly PI/2 larger than 4)? Even with a huge offset in the sinus curve, an increase in PI/2 should still produce a different number right?

  1. Checking small number SIN(2*PI)

    print math.sin(math.pi*2)
    

    RESULT = -2.44929359829e-16 AS EXPECTED → This error margin is OK for my purpose

  2. Adding PI/2 to code above: SIN(2*PI + PI/2)

    print math.sin((math.pi*2)+(math.pi/2))
    

    RESULT: 1.0 AS EXPECTED

  3. Checking very large number SIN(2*PI*VERY LARGE NUMBER) (still expecting close to zero)

    print math.sin(math.pi*2*(415926535897932384626433832795028841971693993751))
    

    RESULT:-0.759488037749 NOT AS EXPECTED --> This error margin is NOT OK for my purpose

  4. Adding PI/2 to code above: SIN(2*PI*VERY LARGE NUMBER + PI/2) (expecting close to one)

    print math.sin((math.pi*2*(415926535897932384626433832795028841971693993751))+(math.pi/2))
    

    As above but I added PI/2 - expecting to get 1.0 as result
    RESULT:-0.759488037749 NOT AS EXPECTED - why the same result as above when I added PI/2 (should go a quarter period on the sinus curve)

  5. Adding random number (8) to the very large number, expecting neither 1 nor 0

    print math.sin(math.pi*2*(415926535897932384626433832795028841971693993759))
    

    as above but I added 8 - expecting to get neither 0 nor 1
    RESULT:-0.759488037749 NOT AS EXPECTED - why the same result as above when I added 8

3 Answers

This simply isn't going to work with double-precision variables.

The value of math.pi is correct only to about 16 places of decimals (53 bits in binary), so when you multiply it by a number like 415926535897932384626433832795028841971693993751 (159 bits), it would be impossible to get meaningful results.

You need to use an arbitrary precision math library instead. Try using mpmath for example. Tell it you want 1000 bits of precision, and then try your sums again:

>>> import mpmath
>>> mpmath.mp.prec=1000
>>> print(mpmath.sin((mpmath.pi*2*(415926535897932384626433832795028841971693993751))+(mpmath.pi/2)))
1.0

How to avoid math.sin(math.pi*2*VERY LARGE NUMBER) having a much larger error margin than math.sin(math.pi*2)?

You could % 1 that very large number:

>>> math.sin(math.pi*2*(415926535897932384626433832795028841971693993751))
-0.8975818793257183
>>> math.sin(math.pi*2*(415926535897932384626433832795028841971693993751 % 1))
0.0
>>> math.sin((math.pi*2*(415926535897932384626433832795028841971693993751))+(math.pi/2))
-0.8975818793257183
>>> math.sin((math.pi*2*(415926535897932384626433832795028841971693993751 % 1))+(math.pi/2))
1.0

The algorithms used are approximate, and the values (e.g. pi) are approximate. So $\pi \cdot {SomeLargeNumber}$ will have a largeish error (as $\pi$'s value is approximate). The function used (by hardware?) will reduce the argument, perhaps using a slightly different value of $\pi$.

Note that floating point arithmetic does not satisfy the axioms for real arithmetic.

Related