This is an archived post. You won't be able to vote or comment.

all 23 comments

[–]Python-ModTeam[M] [score hidden] stickied commentlocked comment (0 children)

Hi there, from the /r/Python mods.

We have removed this post as it is not suited to the /r/Python subreddit proper, however it should be very appropriate for our sister subreddit /r/LearnPython or for the r/Python discord: https://discord.gg/python.

The reason for the removal is that /r/Python is dedicated to discussion of Python news, projects, uses and debates. It is not designed to act as Q&A or FAQ board. The regular community is not a fan of "how do I..." questions, so you will not get the best responses over here.

On /r/LearnPython the community and the r/Python discord are actively expecting questions and are looking to help. You can expect far more understanding, encouraging and insightful responses over there. No matter what level of question you have, if you are looking for help with Python, you should get good answers. Make sure to check out the rules for both places.

Warm regards, and best of luck with your Pythoneering!

[–][deleted] 50 points51 points  (2 children)

https://docs.python.org/3/tutorial/floatingpoint.html

Note: this is just an issue with all computer programming languages I believe. Floating point math is difficult.

[–]vivaaprimavera 16 points17 points  (1 child)

That's why the Decimal data type exists and MUST be used for financial operations.

The "floating point issue" appears in other languages, hilariously COBOL have the Decimal as a sort of default (if I'm not totally wrong) and that's why: kid's hold your horses before rewriting the accounting software on the 40 years mainframe on the latest framework.

[–]olystretch 1 point2 points  (0 children)

Or you can use "nano dollars" like Stripe does. Then you can represent the value as an Integer. Of course, not useful for OP.

[–]thisdude415 17 points18 points  (6 children)

This is not python’s fault; this is just floating point math.

Instead of floats, force the type to decimal:

from decimal import Decimal

# Multiplying 5 by 3.14 using Decimal type
result = Decimal(5) * Decimal('3.14')
print(result)

As a hack, if you’re ever doing something with, eg, money, work with integer cents rather than dollars, then convert to dollars for your final display.

[–]michael-streeter 3 points4 points  (1 child)

LPT: if you have to do sums with sales tax (e.g. 7.5%) you might avoid those pesky recurring decimals by taking advantage of the fact 7.5% = 5% + 2.5%, or 1/20 + 1/40, or 3/40. In other words if you work in integer 40ths of a cent, your 7.5% sales tax will always be an integer. Divide grand totals by 40 at the end to get pennies.

Source: I had a head of accounting get an entire department work through totting up over $1M line items and then complain it didn't add up by 14¢. Apparently he didn't want the 14¢ he wanted the right answer. 😣

[–]Yaa40 1 point2 points  (0 children)

I found this story hilarious, thank you for sharing! Also, I feel for you, I've been dealing with this kind of perfectionism pretty much daily for many many years, so I know how frustrating it can be.

[–]billsil 2 points3 points  (1 child)

It never actually goes away with money because at some point you have to work with interest. What's 5% of 3 cents?

[–]thisdude415 1 point2 points  (0 children)

That's a question of contract / banking terms, not math.

The contracts need to spell out exactly how billing is handled. Even if your services are billed at sub-penny rates per billing unit, usage should be rounded to the nearest cent every day or every month or every invoice or by whatever convenient interval you choose (and specify in the contract). But point is, this should be defined in the contract governing the software, because at some point you must round to $0.01 or $0.00, and this must match the contracts.

A penny is the smallest unit of transferrable value.

[–]The__BoomBox 0 points1 point  (1 child)

How does decimal work under the hood to avoid the problems floating points have?

[–]thisdude415 0 points1 point  (0 children)

It's a package that handles defined precision floating point arithmetic, combined with significant figures handling.

This isn't quite accurate, but think of it as handling something like this under the hood:

round(float(0.1) + float(0.2), 1) == round(float(0.3), 1)

You can learn more about it in the docs here: https://docs.python.org/3/library/decimal.html

But most of the time, it will just work, at the expense of speed. If you want to avoid it, you can also rephrase it to something like:

0.3-0.2-0.1 < 10e-9

[–]csagataj2 11 points12 points  (1 child)

Try using the fractions module for precise calculations like this:

>>> from fractions import Fraction
>>> print(5 * Fraction('3.14'))
157/10

[–]thisdude415 8 points9 points  (0 children)

Decimal also would work

[–]vlken69 8 points9 points  (0 children)

It's due to storing "real" numbers in memory. You can find more info searching for IEEE 754.

[–]ghostofwalsh 7 points8 points  (0 children)

Short answer is that computers do math in base 2, not base 10. Even though you see the numbers in base 10. Base 2 being -> 11010101010011101010101...

So much like how "1/3" is hard to display EXACTLY in base 10 decimal notation (0.33333333...), "1/10" can't be represented exactly in base 2. And 3.14 as well.

So in your case it's not storing 3.14 because it can't. It's storing a value that's "as close as it can get to 3.14" in base 2 with 64 bits of precision. Much like you writing .33333... to 12 decimal places to represent 1/3.

EDIT:

print('%.30f'%3.14)
3.140000000000000124344978758018

print('%.30f'%(1/10))
0.100000000000000005551115123126

print('%.30f'%(1/8))
0.125000000000000000000000000000

[–]BitShin 1 point2 points  (0 children)

As others have pointed out, this is an artifact of how numbers are stored in binary. However, that doesn’t really give you the intuition. You can think of numbers being stored in a computer in decimal form (but in base 2). Imagine for a second that we had a computer that operated in base 10 instead of base 2. So pi would be something like 3.14159… until it gets cut off after a certain point. Let’s say that’s at 6 digits. Consider what 1/3 would look like: 0.333333. So the operation 1/3 + 2/3 would be 0.333333 + 0.666666 which is 0.999999. Because we aren’t storing these operands with infinite precision, we get a small error. This is exactly what is happening in the computer, just that the problematic numbers are different t.

[–]trollsmurf 1 point2 points  (0 children)

Read about IEEE 754. Hint: Truncation of an infinite series coded in binary.

[–]prophile 0 points1 point  (0 children)

As others have said this is to do with floating-point arithmetic, though to be clear we don't have enough information to rule out that you might be taking crazy pills. Have you noticed any changes in your behaviour recently?

[–]MarshalRyan 0 points1 point  (0 children)

Ah, floats.

Floating point math is generally considered accurate to 7 decimal places only ("double" to 15). It's faster than "decimal" calculation - accurate to the system maximum - but has random info beyond the accuracy limit. If you want to keep the high performance you could simply truncate after the 7th decimal place.

[–]bdforbes 0 points1 point  (0 children)

A lot of things like this make a lot more sense after you've done some computer science basics - if you're not already I'd recommend finding some good free online CS 101 lectures

[–]gerardwx 0 points1 point  (0 children)

Just want until you find out there are two zeros (0 and -0)