Domanda

I'm reading a JSON data file that might give me a float value of, say, 1.1. When I make a Decimal of that value, I get a crazy long number, because of the imprecision of binary representations of floats.

I understand binary representation and I'm okay with the idea that numbers writable in base-ten floating point can't always be represented in base-two.

But it seems that if I string-ify the float first, and make a Decimal using that string as the value, I get a Decimal without the tiny binary-imprecision delta.

Here's what I mean:

Python 2.7.6 (default, Jan 16 2014, 10:55:32) 
>>> from decimal import Decimal
>>> f = 1.1
>>> d = Decimal(f)
>>> f
1.1
>>> d
Decimal('1.100000000000000088817841970012523233890533447265625')
>>> d = Decimal(str(f))
>>> d
Decimal('1.1')

String-ifying the float before making it into a Decimal seems to give me a result closer to the original base-ten number as typed (or as read in from a JSON file).

So here's my question(s): when stringifying the float, why don't I see the long tail of digits? Is python automagically keeping track of the original string parsed in from the JSON, or something? Why doesn't the Decimal constructor use that trick too?

È stato utile?

Soluzione

The exact value of the float is 1.100000000000000088817841970012523233890533447265625. Python isn't somehow keeping track of the original string. When Python stringifies it with str, it truncates to 12 digits.

>>> x = 1.0/9
>>> print decimal.Decimal(x) # prints exact value
0.111111111111111104943205418749130330979824066162109375
>>> print x # truncated
0.111111111111

Even with repr, Python uses the shortest string that will round to the original float when parsed with float.

>>> print repr(x)
0.1111111111111111

If you want to parse JSON and get Decimal instances instead of float, you can pass a parse_float argument to the load(s) function:

>>> json.loads('{"foo": 1.234567890123456789}', parse_float=decimal.Decimal)
{u'foo': Decimal('1.234567890123456789')}

The above call causes decimal.Decimal to be called to parse numbers in the JSON string, bypassing the rounding that would occur with intermediate float or str calls. You'll get exactly the number specified in the JSON.

Note that the API distinguishes between the functions used to parse things that look like floats, things that look like ints, and things that look like Infinity, -Infinity, or NaN. This can be a bit inconvenient if you want all 3 categories to be handled the same way:

>>> json.loads('{"foo": 1.234567890123456789}',
...            parse_float=decimal.Decimal,
...            parse_int=decimal.Decimal,
...            parse_constant=decimal.Decimal)
{u'foo': Decimal('1.234567890123456789')}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top