-1

This is more of a question out of interest. In my naivete, not knowing exactly how Decimal() is supposed to be used, I thought it would work fine when I did something like Decimal(120.24). However, see the following code:

>>> d = Decimal(120.24)
>>> d
Decimal('120.2399999999999948840923025272786617279052734375')

I changed how my code worked to avoid some problems that were coming up because of this, but my question lies in why this is happening at all. I did a little searching to try and find a straightforward answer, but most questions were asking how to work with the two types and not a more general "why" question.

So, my question is: What is happening behind the scenes that makes the decimal ever so slightly innacurate? Why doesn't it just come out as Decimal('120.24')?

In other words, why does Python itself treat floats different from Decimals? Why are floats "inaccurate"? In most other languages, as far as I know, floats are accurate to a specific point. Definitely more than 2 decimal places.

For reference, I'm using Python 3.6.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Ethan Brouwer
  • 975
  • 9
  • 32
  • 3
    It doesn't, it keeps it exactly the same. The *float* is imprecise, and that's what you're creating the Decimal from. – jonrsharpe Apr 30 '19 at 22:08
  • 2
    That's an answer. Marking this as a duplicate is not an answer. – Ethan Brouwer Apr 30 '19 at 22:08
  • 1
    It is a duplicate though... and the answers to the other question are quite in-depth. – thebjorn Apr 30 '19 at 22:14
  • 1
    @thebjorn: Where in the other answers does it explain that, in Python, `Decimal(120.24)` is evaluated with two conversions to different floating-point formats? – Eric Postpischil Apr 30 '19 at 23:36
  • @EricPostpischil two conversions? This question has been asked so many times it even has its own chapter in the Python tutorial (https://docs.python.org/3/tutorial/floatingpoint.html), but to answer your question, most of the answers to the duplicate either explain directly why `120.24` can't be represented exactly in base 2 (and why); or they link to a variation of "What every Computer Scientist Should Know About Floating-Point Arithmetic (https://www.itu.dk/~sestoft/bachelor/IEEE754_article.pdf). So yes, this question is definitely a duplicate (amongst a forrest of similar duplicates). – thebjorn May 01 '19 at 08:16
  • @thebjorn: Answering “why 20.24 can’t be represented exactly” is not an explanation that `Decimal(120.24)` is evaluated with two conversions to different floating-point formats. So, no, this question is not a duplicate of those. Further, where in the tutorial section you link to does it say that `Decimal(120.24)` is evaluated with two conversions? – Eric Postpischil May 01 '19 at 10:46
  • @EricPostpischil what "two conversions"? `120.24` **is** `120.23999999...`, as you can see with e.g. `format(120.24, ".40f")`. The OP has even now added *"Why are floats "inaccurate"?"* to the question. The fact that the differing default display of Decimal vs. float makes this more obvious doesn't change the fact that this isn't actually where the issue occurs. – jonrsharpe May 01 '19 at 12:26
  • *"In most other languages, as far as I know, floats are accurate to a specific point."* - the relevant spec is mentioned in the dupe; all compliant implementations have the same imprecision. – jonrsharpe May 01 '19 at 12:27
  • @jonrsharpe: The two conversions are that, first, Python converts the source text `120.24` to its general floating-point format, and, second, it then converts that value to the `Decimal` format. `120.24` is not 120.2399999999…; `120.24` is a sequence of the six characters 1, 2, 0, ., 2, and 4. It is written by humans as a decimal numeral, and there is nothing mandatory in how computers or software operates that necessitates its interpretation as a binary floating-point value. That is a choice made by Python (specifically a Python implementation, as Python does not mandate… – Eric Postpischil May 01 '19 at 13:14
  • … a specific floating-point format). Furthermore, it is a choice that was not apparent to OP when they asked the question originally, as the question shows they expected these characters to be interpreted as a decimal number. Therefore, the missing information includes the fact that Python makes this conversion to its general floating-point format before the conversion to `Decimal`. That is a necessary part of a complete answer. – Eric Postpischil May 01 '19 at 13:16
  • 1
    @EricPostpischil I suggest that if you have an answer that is different from the ones in the dupe, then please state it as an answer instead of adding it as a mega-comment. – thebjorn May 01 '19 at 13:40
  • @thebjorn: There is already an answer here, not in the previously suggested originals, that I deem sufficient. I did not enter the comment to serve as an answer to the Stack Overflow question; I entered the most recent comment pair to answer your question about which two conversions and how the previously suggested originals failed to serve. – Eric Postpischil May 01 '19 at 13:47
  • @EricPostpischil ps: for all practical purposes Python specifies floats to be IEEE754 doubles, as the data model docs specifies Python float (numbers.Real) as representing "machine-level double precision floating point numbers" with range and overflow handling according to the machine architecture and C/Java compiler implementation. I'm sure there are processors/compilers where a C double is not a binary floating point value that runs python - although I can't think of any except maybe very old IBM mainframes? afaik, both Java and C require double to be ieee754... – thebjorn May 01 '19 at 14:06
  • @thebjorn: C does not require IEEE-754. – Eric Postpischil May 01 '19 at 14:07
  • @EricPostpischil I know annex f is optional, but are there real compilers that do not implement it? – thebjorn May 01 '19 at 14:09
  • @thebjorn: How is this relevant to this question? The fact is that `Decimal(120.24)` does not produce exactly 120.24 is caused by the fact that Python converts first to its general floating-point format and then to `Decimal`. The specifics of what that general floating-point format are are not particularly relevant. The mere fact that 120.24 is not exactly representable in it (in the OP’s Python implementation) suffices to cause the change, whether it is IEEE 754 or something else. – Eric Postpischil May 01 '19 at 14:12
  • @EricPostpischil ..I believe "all" the answers to the duplicate were variations of: the real number 120.24 is not representable as a Python float, which is and has always been a ieee754 double, thus when you write 120.24 you get the closest representable double value. I can't see that you've added anything new to this..? I'm not a floating point guru however, so I might be missing something (so please add an in-depth answer so this can become the canonical reference for future questions). – thebjorn May 01 '19 at 14:22
  • @thebjorn: Look at your text “when you wrote 120.24 you get the closest representable value.” That is not, in general, a true statement. When I write “120.24” on a piece of paper, I have “120.24.” When I interpret it as a decimal numeral, I have 120.24. When I type it into, say, Maple software, I have 120.24. It is not, in general, true that when I write “120.24,” I have the closest value representable in IEEE 754. The time when you or I write “120.24” and have the closest value representable in IEEE 754 is **when software reads the text and converts it to IEEE 754**… – Eric Postpischil May 01 '19 at 14:27
  • … That is a specific, definite act performed by a computer. It is not automatic, it is not inevitable, it is not done by all software, it was not apparent to OP in the original question, and it is not stated in the previously suggested originals that **Python performs this step when it is interpreting the source text `Decimal(120.24)`**. I really do not see what is hard to understand about that. There are characters that are interpreted by the computer; the computer performs this specific step of interpretation. OP did not know that, and it needs to be said as part of the answer. – Eric Postpischil May 01 '19 at 14:28
  • @EricPostpischil we're going in circles so this will be my last comment. You seem to be making a philosophical rather than technical point -- yes, when you write Maple programs they're different than Python, and Python source code is converted to Python programs, not Maple programs... I'm not sure which point you're trying to make (that Python could have been another language with different float semantics?), however, the quick start tutorial for Decimals cover this exact topic in the context of Python: https://docs.python.org/3.7/library/decimal.html#quick-start-tutorial – thebjorn May 01 '19 at 14:39
  • @thebjorn: It is not the least bit philosophical. Python works in a specific, mechanical way. For `Decimal(120.24)`, it converts `120.24` to its general floating-point format. That is a specific step that Python takes. In order to understand Python semantics, one must be aware of that step. In order to explain how `120.24` becomes a number different from 120.24, **it must be stated that that step is performed**. If an answer does not say that, it is not answering. Regardless of whatever text is in the tutorial, the information of a Stack Overflow answer is supposed to be in the answer itself. – Eric Postpischil May 01 '19 at 14:52
  • @thebjorn: You mention “Python could have been another language with different float semantics.” How is anybody to know what the semantics are (not just floating-point semantics but how `Decimal(120.24)` is parsed) unless somebody says what they are? You cannot just take it as a given that Python semantics are whatever they are and expect everybody to know them innately. They have to be stated. When there is a question whose answer depends on what the semantics are, stating what the semantics are is part of the answer. – Eric Postpischil May 01 '19 at 14:59

1 Answers1

4

The conversion to Decimal was exact. 120.2399999999999948840923025272786617279052734375 is the closest IEEE754 64-bit float to the real number 120.24, and therefore the exact value of d.

You can avoid the conversion to floating point by passing a string to Decimal:

d = Decimal("120.24")
print (d)

prints 120.24

See decimal Decimal fixed point and floating point arithmetic for more discussion and a tutorial.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75