Reputation: 16115
From the Python tutorial:
Unfortunately, most decimal fractions cannot be represented exactly as binary fractions. A consequence is that, in general, the decimal floating-point numbers you enter are only approximated by the binary floating-point numbers actually stored in the machine.
I wonder how can I check if a given decimal fraction will be represented exactly as a Python float
. For instance, 0.25
can be represented exactly while 0.1
can't:
>>> 0.1 + 0.1 + 0.1 == 0.3
False
>>> 0.25 + 0.25 + 0.25 == 0.75
True
Upvotes: 3
Views: 1381
Reputation: 1123490
You could use the fractions
module to check if a given fraction can be represented:
from fractions import Fraction
def can_be_represented(num, den):
f = Fraction(num, den)
return Fraction.from_float(float(f)) == f
Because floating point numbers use binary fractions, you'll soon find that this can be simplified to checking for a denominator that is a power of two:
def can_be_represented(num, den):
f = Fraction(num, den)
return f.denominator & (f.denominator - 1) == 0
However, this doesn't make any bounds checks on the numerator, add a bounds check by comparing with the information from sys.float_info
:
import sys
def can_be_represented(num, den):
f = Fraction(num, den)
return (
# denominator is a power of 2
f.denominator & (f.denominator - 1) == 0 and
# numerator exponent can be represented
f.numerator.bit_length() <= sys.float_info.max_exp and
# numerator significant bits can be represented without loss
len(format(f.numerator, 'b').rstrip('0')) <= sys.float_info.mant_dig
)
The above version tests:
An optimised but less readable version of the above is:
def can_be_represented(num, den,
_mexp=sys.float_info.max_exp,
_mdig=sys.float_info.mant_dig):
f = Fraction(num, den)
num, den = f.numerator, f.denominator
numbl = num.bit_length()
return (
# denominator is a power of 2
den & (den - 1) == 0 and
# numerator exponent can be represented
numbl <= _mexp and
# numerator significant bits can be represented without loss
(numbl <= _mdig or num << numbl - _mdig >> numbl - _mdig == num)
)
Upvotes: 4
Reputation: 9412
In Squeak Smalltalk, you would find this method:
Fraction>>isAnExactFloat
"Answer true if this Fraction can be converted exactly to a Float"
^ denominator isPowerOfTwo
and: ["I have a reasonable significand: not too big"
numerator highBitOfMagnitude <= Float precision
and: ["I have a reasonable exponent: not too small"
Float emin + denominator highBitOfMagnitude <= Float precision]]
and
Integer>>isAnExactFloat
"Answer true if this Integer can be converted exactly to a Float"
| h |
(h := self highBitOfMagnitude) <= Float precision
ifTrue: [^ true].
^ h - 1 <= Float emax
and: [h - self abs lowBit < Float precision]
Of course it's not Python, but it's the same underlying floating point, so that shouldn't be that hard to translate...
Upvotes: 1
Reputation: 149991
On that very page:
On most machines today, floats are approximated using a binary fraction with the numerator using the first 53 bits starting with the most significant bit and with the denominator as a power of two.
Thus for a decimal fraction to be exactly representable as float
it must be a fraction with a denominator which is a power of 2:
>>> 1/10 + 1/10 + 1/10 == 3/10 # 0.1 + 0.1 + 0.1
False
>>> 1/4 + 1/4 + 1/4 == 3/4 # 0.25 + 0.25 + 0.25
True
Upvotes: 3