tags:

views:

6708

answers:

6

Python Decimal doesn't support being constructed from float, it expects that you have to convert float to a string first.

This is very inconvenient since standard string formattors for float require that you specify number of decimal places rather than significant places. So if you have a number that could have as many as 15 decimal places you need to format as Decimal(""%.15f"% my_float), which will give you garbage at the 15th decimal place if you also have any significant digits before decimal.

Can someone suggest a good way to convert from float to Decimal preserving value as the user has entered, perhaps limiting number of significant digits that can be supported.

+1  A: 

The "official" string representation of a float is given by the repr() built-in:

>>> repr(1.5)
'1.5'
>>> repr(12345.678901234567890123456789)
'12345.678901234567'

You can use repr() instead of a formatted string, the result won't contain any unnecessary garbage.

Federico Ramponi
repr returns 17 significant places, but there are often errors in 16th and 17th significant place. So the following does not work.from decimal import Decimalstart = Decimal('500.123456789016')assert start == Decimal(repr(float(start))), "%s != %s " % (start, Decimal(repr(float(start))))
Kozyarchuk
@Kozyarchuk: This is not an issue with the number of significant digits. It's a problem with binary representation of decimal numbers. Your example fails for 0.6, too.
davidavr
+6  A: 
"%.15g" % f

Or in Python 3.0:

format(f, ".15g")
J.F. Sebastian
+9  A: 

You said in your question:

Can someone suggest a good way to convert from float to Decimal preserving value as the user has entered

But every time the user enters a value, it is entered as a string, not as a float. You are converting it to a float somewhere. Convert it to a Decimal directly instead and no precision will be lost.

nosklo
Unfortunately it's stored as float in the DB and I can't change the schema
Kozyarchuk
Then you have already lost “the value as the user has entered” and you can't get it back. All you can do is apply an arbitrary rounding and hope. Python's treatment of Decimal correctly brings this to your attention so you understand the problem now instead of getting a weird bug later.
bobince
@Kozyarchuk: I know you said that you can't change the schema, but you may want to think about changing it to a decimal type (at some point), because this can become more of a headache in the future.
Jeremy Cantrell
A: 

The "right" way to do this was documented in 1990 by Steele and White's and Clinger's PLDI 1990 papers.

You might also look at this SO discussion about Python Decimal, including my suggestion to try using something like frap to rationalize a float.

Doug Currie
+2  A: 

When you say "preserving value as the user has entered", why not just store the user-entered value as a string, and pass that to the Decimal constructor?

Paul Fisher
+2  A: 

Python does support Decimal creation from a float. You just cast it as a string first. But the precision loss doesn't occur with string conversion. The float you are converting doesn't have that kind of precision in the first place. (Otherwise you wouldn't need Decimal)

I think the confusion here is that we can create float literals in decimal format, but as soon as the interpreter consumes that literal the inner representation becomes a floating point number.

muhuk