First, let's review what epsilon
really is in the return value of sys.float_info
.
Epsilon (or
) is the smallest number such that 0.5 + ≠ 0.5 AND 0.5 - ≠ 0.5
Python is telling you that the smallest number that will cause 0.5
to increment or decrement repeatably is epsilon=2.220446049250313e-16
-- but this is only for the value 0.5. You are attempting to increment 1.0
by 1.0e-17
. This is a larger value (1.0 vs 0.5) being incremented by a smaller number than the for 0.5 (1.0e-17 vs 2.2e-16). You are off by an order of magnitude roughly, since the increment value of 1.0e-17 is an order of magnitude smaller than the relative epsilon for 1.0.
You can see this here:
These change the value of 0.5
>>> 0.5+sys.float_info.epsilon
0.5000000000000002
>>> 0.5-sys.float_info.epsilon
0.4999999999999998
These values do not:
>>> 0.5+sys.float_info.epsilon/10.0
0.5
>>> 0.5-sys.float_info.epsilon/10.0
0.5
>>> 5.0+sys.float_info.epsilon
5.0
>>> 5.0-sys.float_info.epsilon
5.0
Explanation:
IEEE 754 defines the floating point format in use today on most standard computers (specialty computers or libraries may use a different format.) The 64 bit format of IEEE 754 uses 53 bits of precision to calculate and 52 to store to the mantissa of a floating point value. Since you have a fixed 52/53 bits to work with, the magnitude and accuracy of the mantissa changes for larger / smaller values. So then the changes as the relative magnitude of a floating point number changes. The value of for 0.5 is different that the value for 1.0 and for 100.0.
For a variety of very good and platform-specific reasons (storage and representation, rounding, etc), even though you could use a smaller number, epsilon is defined as using 52 bits of precision for the 64 bit float format. Since most Python implementations use a C double float for float, this can be demonstrated:
>>> 2**-52==sys.float_info.epsilon
True
See how many bits your platform will do:
>>> 0.5 + 2.0**-53
0.5000000000000001
>>> 0.5 - 2.0**-53
0.4999999999999999
>>> 0.5 + 2.0**-54
0.5 # fail for 0.5 + 54 bits...
>>> 0.5 - 2.0**-54
0.49999999999999994 # OK for minus
>>> 0.5 - 2.0**-55
0.5 # fail for 0.5 minus 55 bits...
There are several work arounds for your issue:
- You can use the C99 concept of nextafter to calculate the value appropriate epsilon. For Python, either use numpy or the Decimal class to calculate
nextafter
. More on nextafter
in my previous answer HERE
- Use integers. A 64 bit integer will clearly handle an epsilon value in the 17th order of magnitude without rounding.
- Use an arbitrary precision math library. Decimal is in the standard Python distribution.
The important concept is that the value of is relative to value (and if you are incrementing or decrementing).
This can be seen here:
>>> numpy.nextafter(0.0,1.0)-0.0
4.9406564584124654e-324 # a relative epsilon value of 4.94e-324
>>> numpy.nextafter(0.01,1.0)-0.01
1.7347234759768071e-18 # 1e-17 would still work...
>>> numpy.nextafter(0.1,1.0)-0.1
1.3877787807814457e-17 # 1e-17 would >>barely<< work...
>>> numpy.nextafter(0.5,1.0)-0.5
1.1102230246251565e-16 # a relative epsilon value of 1.1e-16
>>> numpy.nextafter(500.0,501.0)-500.0
5.6843418860808015e-14 # relative epsilon of 5.6e-14
>>> numpy.nextafter(1e17,1e18)-1e17
16.0 # the other end of the spectrum...
So you can see that 1e-17 will work handily to increment values between 0.0 and 0.1 but not many values greater than that. As you can see above, the relative for 1e17 is 16.