Re: Question about floating point
On Sat, 1 Sep 2018 at 12:31, Frank Millman wrote: > > "Frank Millman" wrote in message news:pm3l2m$kv4$1...@blaine.gmane.org... > > > > I know about this gotcha - > > > > >>> x = 1.1 + 2.2 > > >>> x > > 3.3003 > > > [...] > > I have enjoyed the discussion, and I have learnt a lot about floating point. > Thanks to all. > > I have just noticed one oddity which I thought worth a mention. > > >>> from decimal import Decimal as D > >>> f"{D('1.1')+D('2.2'):.60f}" > '3.3000' > >>> '{:.60f}'.format(D('1.1') + D('2.2')) > '3.3000' > >>> '%.60f' % (D('1.1') + D('2.2')) > '3.2998223643160599749535322189331054687500' > >>> > > The first two format methods behave as expected. The old-style '%' operator > does not. > > Frank Presumably, Decimal has a custom formatting method. The old-style % formatting doesn't support custom per-class formatting, so %.60f converts its argument to float and then prints it. Paul -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Sat, 01 Sep 2018 13:27:59 +0200, Frank Millman wrote: from decimal import Decimal as D f"{D('1.1')+D('2.2'):.60f}" > '3.3000' '{:.60f}'.format(D('1.1') + D('2.2')) > '3.3000' '%.60f' % (D('1.1') + D('2.2')) > '3.2998223643160599749535322189331054687500' > The first two format methods behave as expected. The old-style '%' > operator does not. The % operator casts the argument to a (binary) float. The other two don't need to, because they call Decimal's own format method. -- Steven D'Aprano "Ever since I learned about confirmation bias, I've been seeing it everywhere." -- Jon Ronson -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
"Frank Millman" wrote in message news:... "Frank Millman" wrote in message news:pm3l2m$kv4$1...@blaine.gmane.org... I know about this gotcha - >>> x = 1.1 + 2.2 >>> x 3.3003 [...] I have enjoyed the discussion, and I have learnt a lot about floating point. Thanks to all. I have just noticed one oddity which I thought worth a mention. from decimal import Decimal as D f"{D('1.1')+D('2.2'):.60f}" '3.3000' '{:.60f}'.format(D('1.1') + D('2.2')) '3.3000' '%.60f' % (D('1.1') + D('2.2')) '3.2998223643160599749535322189331054687500' The first two format methods behave as expected. The old-style '%' operator does not. Frank -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Fri, 31 Aug 2018 18:45:16 +1200, Gregory Ewing wrote: > Steven D'Aprano wrote: >> The right way is to >> set the rounding mode at the start of your application, and then let >> the Decimal type round each calculation that needs rounding. > > It's not clear what you mean by "rounding mode" here. If you mean > whether it's up/down/even/whatever, then yes, you can probably set that > as a default and leave it. I mean the rounding mode :-) https://docs.python.org/3/library/decimal.html#rounding-modes > However, as far as I can see, Decimal doesn't provide a way of setting a > default number of decimal places to which results are rounded. You can > set a default *precision*, but that's not the same thing. Indeed it is not. That's a very good point, and I had completely forgotten about it! Thank you. The quantize method is intended for the use-case we are discussing, to round values to a fixed number of decimal places. The Decimal FAQs mention that: https://docs.python.org/3/library/decimal.html#decimal-faq I think this is a good use-case for subclassing Decimal as a Money class. [...] > I don't think this is a bad thing, because often you don't want to use > the same number of places for everything, For example, people dealing > with high-volume low-value goods often calculate with unit prices having > more than 2 decimal places. In those kinds of situations, you need to > know exactly what you're doing every step of the way. As opposed to anyone else calculating with money? -- Steven D'Aprano "Ever since I learned about confirmation bias, I've been seeing it everywhere." -- Jon Ronson -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
Steven D'Aprano wrote: The right way is to set the rounding mode at the start of your application, and then let the Decimal type round each calculation that needs rounding. It's not clear what you mean by "rounding mode" here. If you mean whether it's up/down/even/whatever, then yes, you can probably set that as a default and leave it. However, as far as I can see, Decimal doesn't provide a way of setting a default number of decimal places to which results are rounded. You can set a default *precision*, but that's not the same thing. (Precision is the total number of significant digits, not the number of digits after the decimal point.) So if you're working with dollars and cents and want all your divisions rounded to 2 places, you're going to have to do that explicitly each time. I don't think this is a bad thing, because often you don't want to use the same number of places for everything, For example, people dealing with high-volume low-value goods often calculate with unit prices having more than 2 decimal places. In those kinds of situations, you need to know exactly what you're doing every step of the way. We have here a brilliant hammer specially designed for banging in just this sort of nail, Except that we don't, we actually have an impact screwdriver, so you've going to have to bring your hammer to deal with nails properly. And a single size of hammer isn't going to suit all kinds of nail. -- Greg -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Thu, 30 Aug 2018 19:22:29 +1200, Gregory Ewing wrote: > Steven D'Aprano wrote: >> Why in the name of all that's holy would anyone want to manually round >> each and every intermediate calculation when they could use the Decimal >> module and have it do it automatically? > > I agree that Decimal is the safest and probably easiest way to go, but > saying that it "does the rounding automatically" is a bit misleading. > > If you're adding up dollars and cents in Decimal, no rounding is needed > in the first place, because it represents whole numbers of cents exactly > and adds them exactly. "Round to exact" is still rounding :-P I did already say that addition and subtraction was exact in Decimal. (I also mentioned multiplication, but that's wrong.) > If you're doing something that doesn't result in a whole number of cents > (e.g. calculating a unit price from a total price and a quantity) you'll > need to think about how you want it rounded, and should probably include > an explicit rounding step, if only for the benefit of someone else > reading the code. If you're not using Banker's Rounding for financial calculations, you're probably up to no good *wink* Of course with Decimal you always have to option to round certain calculations by hand, if you have some specific need to. But in general, that's just annoying and error-prone book-keeping. The right way is to set the rounding mode at the start of your application, and then let the Decimal type round each calculation that needs rounding. The whole point of Decimal, the reason it was invented, was to do this sort of thing. We have here a brilliant hammer specially designed for banging in just this sort of nail, and you're saying "Well, sure, but you probably want to bang it in with your elbow, if only for the benefit of onlookers..." :-) -- Steven D'Aprano "Ever since I learned about confirmation bias, I've been seeing it everywhere." -- Jon Ronson -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
Steven D'Aprano wrote: Why in the name of all that's holy would anyone want to manually round each and every intermediate calculation when they could use the Decimal module and have it do it automatically? I agree that Decimal is the safest and probably easiest way to go, but saying that it "does the rounding automatically" is a bit misleading. If you're adding up dollars and cents in Decimal, no rounding is needed in the first place, because it represents whole numbers of cents exactly and adds them exactly. If you're doing something that doesn't result in a whole number of cents (e.g. calculating a unit price from a total price and a quantity) you'll need to think about how you want it rounded, and should probably include an explicit rounding step, if only for the benefit of someone else reading the code. -- Greg -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Wed, 29 Aug 2018 11:31:29 +1200, Gregory Ewing wrote: > Frank Millman wrote: >> I have been trying to explain why >> they should use the decimal module. They have had a counter-argument >> from someone else who says they should just use the rounding technique >> in my third example above. > > It's possible to get away with this by judicious use of rounding. > There's a business software package I work with that does this -- every > number is rounded to a defined number of decimal places before being > stored in its database, so the small inaccuracies resulting from inexact > representations don't get a chance to accumulate. This software package doesn't actually use the *10/10 trick, does it? As an answer to the question, "Should I use this clever *10/10 trick?" I'm not sure it's relevant to say "Yep, sure, this package does something completely different and it works fine!" *wink* > If you're going to do this, I would NOT recommend using the rounding > technique in your example -- it seems to itself be relying on accidents > of the arithmetic. Use the round() function: Or better still, DON'T manually use the round function, let the interpreter do the rounding for you by using Decimal. That's what its for. Why in the name of all that's holy would anyone want to manually round each and every intermediate calculation when they could use the Decimal module and have it do it automatically? -- Steven D'Aprano "Ever since I learned about confirmation bias, I've been seeing it everywhere." -- Jon Ronson -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Tue, 28 Aug 2018 16:47:25 +0200, Frank Millman wrote: > The reason for my query is this. I am assisting someone with an > application involving monetary values. I have been trying to explain why > they should use the decimal module. They have had a counter-argument > from someone else who says they should just use the rounding technique > in my third example above. *head-desk* And this is why we can't have nice things. Presumably this money application doesn't just work on hard-coded literal values, right? So this "programmer" your friend is listening to prefers to write this: money = (a + b)*10/10 instead of: money = a + b presumably because programming isn't hard enough without superstitious ritual that doesn't actually solve the problem. In the second case, you have (potentially) *one* rounding error, due to the addition. In the first case, you get the *exact same rounding error* when you do (a+b). Then you get a second rounding error by multiplying by ten, and a third rounding error when you divide by ten. Now its true that sometimes those rounding errors will cancel. You found an example: py> (1.1 + 2.2)*10/10 == 3.3 True but it took me four attempts to find a counter-example, where the errors don't cancel: py> (49675.23 + 10492.95)*10/10 == 60168.18 False To prove it isn't a fluke: py> (731984.84 + 173.32)*10/10 == 732158.16 False py> (170734.84 - 173.39)*10/10 == 170561.45 False Given that it has three possible three rounding errors instead of one, it is even possible that this "clever trick" could end up being *worse* than just doing a single addition. But my care factor isn't high enough to track down an example (if one exists). For nearly all applications involving money, one correct solution is to use either integer numbers of cents (or whatever the smallest currency you ever care about happens to be). Then all additions, subtractions and multiplications will be exact, without fail, and you only need to worry about rounding divisions. You can minimize (but not eliminate) that by calculating in tenths of a cent, which effectively gives you a guard digit. Or, just use decimal, which is *designed* for monetary applications (among other things). You decide on how many decimal places to keep (say, two, or three if you want a guard digit), a rounding mode (Banker's Rounding is recommended for financial applications), and just do your calculations with no "clever tricks". Add two numbers, then add tax: money = (a+b)*(1+t/100) compared to the "clever trick": money = (a+b)*10/10 * (1 + t)*10/10 Which would you rather do? -- Steven D'Aprano "Ever since I learned about confirmation bias, I've been seeing it everywhere." -- Jon Ronson -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Tue, 28 Aug 2018 at 15:50, Frank Millman wrote: > > "Frank Millman" wrote in message news:pm3l2m$kv4$1...@blaine.gmane.org... > > > > I know about this gotcha - > > > > >>> x = 1.1 + 2.2 > > >>> x > > 3.3003 > > > [...] > > > > >>> y = 3.3 > > >>> y > > 3.3 > > > [...] > > > > >>> z = (1.1 + 2.2) * 10 / 10 > > >>> z > > 3.3 > > Thanks to Chris and Stephen for the replies. > > They were interesting, but actually did not answer the question that I > forgot to ask! > > The reason for my query is this. I am assisting someone with an application > involving monetary values. I have been trying to explain why they should use > the decimal module. They have had a counter-argument from someone else who > says they should just use the rounding technique in my third example above. > > My gut-feel says that they are wrong, but I don't have the knowledge to > prove it. I think a lot depends on what they are going to do with this value > down the line - maybe it is 'safe enough' for their purposes, so I don't > want to overstate my case. Are there edge cases where that rounding method > could fail? That rounding method is not suitable. As mentioned by others you should at least use round() if working with floats. Here's an example that shows that it doesn't work in the way you want: >>> 0.11 - 0.1 0.009995 >>> (0.11 - 0.1) * 10 / 10 0.009995 >>> round(0.11 - 0.1, 2) 0.01 I wouldn't use floats for this though. -- Oscar -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
Frank Millman wrote: I have been trying to explain why they should use the decimal module. They have had a counter-argument from someone else who says they should just use the rounding technique in my third example above. It's possible to get away with this by judicious use of rounding. There's a business software package I work with that does this -- every number is rounded to a defined number of decimal places before being stored in its database, so the small inaccuracies resulting from inexact representations don't get a chance to accumulate. If you're going to do this, I would NOT recommend using the rounding technique in your example -- it seems to itself be relying on accidents of the arithmetic. Use the round() function: >>> 1.1 + 2.2 3.3003 >>> round(1.1 + 2.2, 1) 3.3 Also, if you go this route, always keep in mind *why* it works -- it relies on the actual value always being close enough to a multiple of a power of 10 that it rounds to the correct value when converted to decimal. Are there edge cases where that rounding method could fail? Yes, it's easy to come up with examples that make it fail, e.g. multiplying by a large number, or adding up a few billion monetary amounts before rounding. These things are very unlikely to happen in an accounting application, but they are theoretically possible. So, you can probably make it work if you're careful, but for peace of mind I would still recommend using Decimal, because Python has it, and it eliminates all of these potential problems. -- Greg -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On 08/28/2018 07:11 AM, Frank Millman wrote: Hi all I know about this gotcha - x = 1.1 + 2.2 x 3.3003 According to the docs, the reason is that "numbers like 1.1 and 2.2 do not have exact representations in binary floating point." So when I do this - y = 3.3 y 3.3 what exactly is happening? What is 'y' at this point? Or if I do this - z = (1.1 + 2.2) * 10 / 10 z 3.3 What makes it different from the first example? Thanks Frank Millman https://en.wikipedia.org/wiki/IEEE_754 Python uses what the C folks call doubles, and what IEEE calls a binary64. If you look down at the precision chart, you'll see that for a value V, you have precision limits on the order of (V * 1e-16). If you strip off the 3.3 that you wanted from the result that you got, your error there is 3e-16, and all that's rounded for display, so all you can really talk about is order of magnitude. -- Rob Gaddi, Highland Technology -- www.highlandtechnology.com Email address domain is currently out of order. See above to fix. -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On 2018-08-28 15:11, Frank Millman wrote: Hi all I know about this gotcha - x = 1.1 + 2.2 x 3.3003 According to the docs, the reason is that "numbers like 1.1 and 2.2 do not have exact representations in binary floating point." So when I do this - y = 3.3 y 3.3 what exactly is happening? What is 'y' at this point? Or if I do this - z = (1.1 + 2.2) * 10 / 10 z 3.3 What makes it different from the first example? There's a bit of rounding going on in the last few digits. For example: >>> 1.1 1.1 >>> _ - 1 0.10009 >>> 0.1 0.1 >>> _ * 10 - 1 0.0 -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Wed, Aug 29, 2018 at 12:47 AM, Frank Millman wrote: > They were interesting, but actually did not answer the question that I > forgot to ask! > > The reason for my query is this. I am assisting someone with an application > involving monetary values. I have been trying to explain why they should use > the decimal module. They have had a counter-argument from someone else who > says they should just use the rounding technique in my third example above. > > My gut-feel says that they are wrong, but I don't have the knowledge to > prove it. I think a lot depends on what they are going to do with this value > down the line - maybe it is 'safe enough' for their purposes, so I don't > want to overstate my case. Are there edge cases where that rounding method > could fail? For virtually all situations, the correct solution is neither float nor decimal; the correct way to handle money is an integer, storing a number of cents, yen, pence, or whatever your smallest unit is. In the rare situations where you need fractional cents, you probably need to use decimal.Decimal(), but if you're doing that sort of thing (eg forex), you'll know what your requirements are far better than I do. Everywhere else, integers make way better sense than anything else. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
"Frank Millman" wrote in message news:pm3l2m$kv4$1...@blaine.gmane.org... I know about this gotcha - >>> x = 1.1 + 2.2 >>> x 3.3003 [...] >>> y = 3.3 >>> y 3.3 [...] >>> z = (1.1 + 2.2) * 10 / 10 >>> z 3.3 Thanks to Chris and Stephen for the replies. They were interesting, but actually did not answer the question that I forgot to ask! The reason for my query is this. I am assisting someone with an application involving monetary values. I have been trying to explain why they should use the decimal module. They have had a counter-argument from someone else who says they should just use the rounding technique in my third example above. My gut-feel says that they are wrong, but I don't have the knowledge to prove it. I think a lot depends on what they are going to do with this value down the line - maybe it is 'safe enough' for their purposes, so I don't want to overstate my case. Are there edge cases where that rounding method could fail? Frank -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On 2018-08-28, Frank Millman wrote: x = 1.1 + 2.2 x > 3.3003 > > According to the docs, the reason is that "numbers like 1.1 and 2.2 do not > have exact representations in binary floating point." Right. > So when I do this - > y = 3.3 y > 3.3 > > what exactly is happening? By default, Python shows only a certain number of significant digits (17?), and the decimal value of y rounded to 17 places is 3.3. > What is 'y' at this point? If you want to see the exact value: >>> y = 3.3 >>> y.hex() '0x1.ap+1' Or in decimal, you can request more significant digits than default: >>> '%0.60f' % y '3.2998223643160599749535322189331054687500' > Or if I do this - > z = (1.1 + 2.2) * 10 / 10 z > 3.3 >>> z = (1.1 + 2.2) * 10 / 10 >>> '%0.60f' % y '3.3002664535259100375697016716003417968750' >>> z.hex() '0x1.ap+1' >>> y = 1.1 + 2.2 >>> '%0.60f' % z '3.2998223643160599749535322189331054687500' >>> y.hex() '0x1.a6667p+1' As you can see from the hex values, they differ by one (least significant) bit in the significand. > What makes it different from the first example? The additional multiplication and division operations are not exact, so "* 10 / 10" produces a result that's slightly different than the one you started with. Two things to remember: 1. The actual numbers in the computer are in base-2. The mapping between what you see in base-10 and the real values in base-2 _is_always_exact_. 2. The _operations_ (multiplicaton/division/addition/subtraction) _are_not_always_exact_. Even if you start with values that map exactly from base-10 to base-2 (some do) doing operations on them may not produce an exact result. -- Grant Edwards grant.b.edwardsYow! Spreading peanut at butter reminds me of gmail.comopera!! I wonder why? -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
Hi Frank, One difference is that, in order to carry out the instructions embodied in the first example, the computer has to perform arithmetic. And it adds the binary approximation of 1.1 (which is very slightly wrong) to the binary approximation of 2.2 (which, again, is very slightly wrong). It then prints the denary equivalent to the sum of those two which happens to be very slightly more than 3.3. When you type 3.3, it stores that as the binary approximation of 3.3 (which is y at that stage, to answer your question, and is, again, no surprise, also very slightly wrong) and then prints the denary equivalent of that binary approximation which happens to end up sufficiently close to 3.3 so as to cause it to print 3.3. I hope that helps. The experts around here (of which I am not one) may well point you to the decimal package which allows better handling of such arithmetic, if you are want the computer to behave more like you would want it to behave. Regards, Stephen Tucker. On Tue, Aug 28, 2018 at 3:11 PM, Frank Millman wrote: > Hi all > > I know about this gotcha - > > x = 1.1 + 2.2 x >>> 3.3003 > > According to the docs, the reason is that "numbers like 1.1 and 2.2 do not > have exact representations in binary floating point." > > So when I do this - > > y = 3.3 y >>> 3.3 > > what exactly is happening? What is 'y' at this point? > > Or if I do this - > > z = (1.1 + 2.2) * 10 / 10 z >>> 3.3 > > What makes it different from the first example? > > Thanks > > Frank Millman > > > > -- > https://mail.python.org/mailman/listinfo/python-list > -- https://mail.python.org/mailman/listinfo/python-list
Re: Question about floating point
On Wed, Aug 29, 2018 at 12:11 AM, Frank Millman wrote: > Hi all > > I know about this gotcha - > x = 1.1 + 2.2 x > > 3.3003 > > According to the docs, the reason is that "numbers like 1.1 and 2.2 do not > have exact representations in binary floating point." > > So when I do this - > y = 3.3 y > > 3.3 > > what exactly is happening? What is 'y' at this point? > > Or if I do this - > z = (1.1 + 2.2) * 10 / 10 z > > 3.3 > > What makes it different from the first example? > Here's how you can find out *exactly* what a Python float is: >>> y = 3.3 >>> y.as_integer_ratio() (3715469692580659, 1125899906842624) >>> x = 1.1 + 2.2 >>> x 3.3003 >>> x.as_integer_ratio() (7430939385161319, 2251799813685248) >>> z = (1.1 + 2.2) * 10 / 10 >>> z 3.3 >>> z.as_integer_ratio() (3715469692580659, 1125899906842624) >>> (1.1).as_integer_ratio() (2476979795053773, 2251799813685248) >>> (2.2).as_integer_ratio() (2476979795053773, 1125899906842624) The value of y is exactly (3715469692580659 / 1125899906842624) which is 3.299822364316060. But x is a teeny bit higher. You can explore these with the help of decimal.Decimal, too. The values given by as_integer_ratio() are possibly more interesting in hex than in decimal, with values like these. -- https://mail.python.org/mailman/listinfo/python-list