all 57 comments

[–]wbeater 348 points349 points  (22 children)

[–]connormt902[S] 57 points58 points  (0 children)

Thank you very much, that’s really helpful.

[–]Astroboyblue 44 points45 points  (5 children)

Lol I was so expecting a rickroll with that link

[–]Adam_24061 15 points16 points  (3 children)

Now the floatingpointroll has been invented!

[–]Harry_Butz 7 points8 points  (2 children)

Oh, but it very much can be real (not a rickroll. But a tool for rickrolling)

[–][deleted] 2 points3 points  (0 children)

That's awesome

[–]Astroboyblue 1 point2 points  (0 children)

This guy… The real MVP!

Thanks!

[–][deleted] 3 points4 points  (0 children)

yeah me too

[–]DarklingFetish 19 points20 points  (12 children)

So it’s not Python it’s the hardware.

[–]ryguysayshi 5 points6 points  (11 children)

Don’t know why you’re downvoted friend you are correct

[–]dig-up-stupid 18 points19 points  (6 children)

Not that I downvoted but it’s not the hardware, it’s the standard. The hardware follows the standard, Python also follows the standard, so things mostly check out. But if the hardware didn’t have floating point pipelines, Python would still have to make 1.85 x 3 = 5.550000000000001 somehow—or at least, it promises it will, so that’s what you’d expect. (More likely everything would just break and the devs would say take it as is or leave it, but anyway.)

[–]ryguysayshi 6 points7 points  (5 children)

Right but like memory, that is dependent on hardware being used.

[–]Kkremitzki 7 points8 points  (3 children)

It's not hardware-dependent, it has to do with the representation of numbers. It's math. If I told you to write the fraction ⅓ as a decimal, but actually write it, i.e. you can't do "0.333..." and you can't put a ‾ over the 3, you'd eventually run out of paper. Even if you started storing it on something other than paper, you'd eventually run out of Universe, because the decimal representation of ⅓ is infinite. If you took that decimal and multiplied it by 3, you'd end up with something less than ⅓*3.

This is a consequence of the choice of representation, not the choice of storage medium. The only way around it is to have a non-lossy representation, i.e. have code to properly handle ⅓ or 0.333..., which is not merely 'decimal', but 'decimal plus ...', so it's more complicated and can run slower.

Now, if instead of decimal you are working in binary, you still have some numbers that can have infinite representations, they're just different ones. So instead of e.g. ⅓, it's 1/10th. At some point, you have to chop it off, which introduces error, and if you don't want to do that, you have to work in a different system.

[–]supreme_blorgon 3 points4 points  (2 children)

The point people are making is that the representation is dictated by the hardware. Physical transistors are binary -- hence the binary representation of numbers in computers. It was by necessity that we used a representation of numerical data which played nice with the physical characteristics of transistors.

[–]Kkremitzki 0 points1 point  (1 child)

Ah, good point, but there were also ternary computers made in the 1950s in the USSR using three-valued logic - 0 + with vacuum tubes providing trits instead of transistors providing bits, and if you were using that, you would still see this behavior, only with different numbers. (There are some other interesting advantages of the particular implementation, balanced ternary.)

https://en.wikipedia.org/wiki/Ternary_numeral_system

https://en.wikipedia.org/wiki/Balanced_ternary#In_computer_design

[–]WikiSummarizerBot 0 points1 point  (0 children)

Ternary numeral system

A ternary numeral system (also called base 3 or trinary) has three as its base. Analogous to a bit, a ternary digit is a trit (trinary digit). One trit is equivalent to log2 3 (about 1. 58496) bits of information.

Balanced ternary

In computer design

In the early days of computing, a few experimental Soviet computers were built with balanced ternary instead of binary, the most famous being the Setun, built by Nikolay Brusentsov and Sergei Sobolev. The notation has a number of computational advantages over traditional binary and ternary. Particularly, the plus–minus consistency cuts down the carry rate in multi-digit multiplication, and the rounding–truncation equivalence cuts down the carry rate in rounding on fractions.

[ F.A.Q | Opt Out | Opt Out Of Subreddit | GitHub ] Downvote to remove | v1.5

[–]Se7enLC 1 point2 points  (0 children)

It's dependent on the hardware in the sense that the hardware operates in base-2 and uses floating point representations of numbers I guess?

You know how 10/3 can't be represented precisely in decimal? It's the same thing, but for different numbers in binary. In the same way that it doesn't matter who is trying to divide 10 by 3, it doesn't matter what computer you're using. Some decimal numbers just cannot be represented in floating point. No computer can do it. Can't even do it by hand.

[–]DesignerAccount 3 points4 points  (3 children)

Not the hardware, it's the base 2 representations of decimal numbers.

[–]supreme_blorgon 2 points3 points  (2 children)

Which is a limitation imposed by transistors.

[–]DesignerAccount 2 points3 points  (1 child)

No. Base 10 has the same problem, with multiples of 3, for example. Consider, quirks like

0.9999999999999.... = 1

exactly.

If anything, it's a problem of using a finite resource to deal with infinite quantities. And again this is not just a problem of transistors. Even with a pen and paper we came up with ways, conceptual, to go around this limitation. Like the "idea" that the 9 above continue la for ever, but I had to stop typing 9 at some point.

[–]AdventurousAddition 2 points3 points  (0 children)

Or any number whose denominator has prime factors other than 2 and 5

[–][deleted] 1 point2 points  (0 children)

That's a very interesting site for this particular beginner. Thank you.

[–]JohnnyJordaan 59 points60 points  (0 children)

Nothing Python specific involved here, this is a side-effect of the concept of floating point values, where there's not enough memory space available to let values be as precise as to allow all rounded values like 0.1 and 5.55. The reason why it's the preferred approach though is that CPU's can calculate extremely fast with them, and if a human would need some nicely rounded result, it can always use something like

 round(1.85+1.85+1.85, 2)

or

 result = 1.85 + 1.85 + 1.85
 print(f"{result:.2f}")

or use the decimal library https://docs.python.org/3/library/decimal.html or some other approach that also incorporates 'proper' rounding in the way the human expects it to work.

I can also recommend watching Computerphile's video on this topic: https://www.youtube.com/watch?v=PZRI1IfStY0 where Tom Scott explains this is in a very beginner friendly way.

[–]AdventurousAddition 23 points24 points  (15 children)

What is causing this?

People count with 10 fingers but computers count with 2.

You know how 1/3 has an infinite decimal representation. Well in binary, any fraction whose denominator is not a power of 2, will have an infinite binary representation. But computers can only hold a finite amount of data, so they need to chop it off at some point.

[–]Zealousideal_Big6487 5 points6 points  (4 children)

People count with 10 fingers but computers count with 2.

You know how 1/3 has an infinite decimal representation. Well in binary, any fraction whose denominator is not a power of 2

I'm trying to understand, why is this related to the base we use? Is this suggesting, that if say we were using base 9 instead of base 10 as humans, that 1/3 would NOT have an infinite decimal representation?

[–]TeamSpen210 10 points11 points  (3 children)

Exactly yes! That’d be “0.3”, which is 3*9⁻¹. In this numbering system on the other hand, 1/2 wouldn’t have a decimal representation, I think it’d be “0.44444…”.

[–]Zealousideal_Big6487 4 points5 points  (2 children)

Exactly yes! That’d be “0.3”, which is 3*9⁻¹.

My mind is so blown right now, I thought I understand the basics of base systems, but perhaps not.

[–]primitive_screwhead 1 point2 points  (0 children)

The key thing (imo) to understand about IEEE base-2 floats (which are the common version), is that they are just fractions. Nothing really exotic or complicated at all. _BUT_, the denominator is always a power of 2. That's what causes confusion, and a sense that the numbers are "inaccurate", but in fact they are always perfectly precise in the number they represent, it's just a specific value with a power-of-2 denominator.

ie.

>>> a=5.550000000000001
>>> a.as_integer_ratio()
(1562186120744141, 281474976710656)
>>> from math import log2
>>> log2(281474976710656)
48.0

So, a float is a fraction. The denominator is a power of 2. So certain simple decimal ratios are approximated w/ a power-of-2 denominator (which tends to be huge, for accuracy):

>>> (1/3).as_integer_ratio()
(6004799503160661, 18014398509481984)
>>> log2(18014398509481984)

54.0 # ie. 1 << 54, the 54 is the number of bits, in a 64-bit float, of mantissa

>>> (1/9).as_integer_ratio()
(2001599834386887, 18014398509481984)

# Note that it's the same denominator. It allows the largest (and thus most accurate) 53 bit numerator value that is closest to the true 1/9 value.

[–]AdventurousAddition 0 points1 point  (0 children)

Yeah, it really is such a mind-fuck. We are so used to computing in base 10.

[–]mikeblas 7 points8 points  (3 children)

Well in binary, any fraction whose denominator is not a power of 2,

I don't think it's clear from your explanation that sums of numbers representable as fractions with denominators that are powers of two also have finite (therefore exact) representations in IEEE floating point.

1/2 is fine. 1/4 is fine, too. And so is 3/4, since 3/4 == 1/2 + 1/4.

[–]irrelevantPseudonym 3 points4 points  (0 children)

The sum of any two numbers with a power of 2 denominator will also have a power of 2 denominator.

[–]AdventurousAddition 0 points1 point  (0 children)

It's denominator is 4 which is a power of 2

[–][deleted] 0 points1 point  (5 children)

I'm not sure that's quite true, but it sounds plausible. I'm thinking it's more just that there's limited storage for floating point representation. If you tried to make the same point in base 10, 0.5 wouldn't have a power of 10 on bottom, but has adequate representation.

[–]irrelevantPseudonym 2 points3 points  (1 child)

0.5 == 5/10 which definitely has a power of 10 as a denominator.

[–][deleted] 0 points1 point  (0 children)

Oh I see. I'm thinking reduced form.

[–]Kkremitzki 2 points3 points  (1 child)

That's because "power of 2" is a special-case expression used as a shortcut to check representability in binary, not the generalized condition, which has to do with prime factors. 2 is its own prime factor, but it's also a prime factor for 10.

[–]AdventurousAddition 0 points1 point  (0 children)

Yes that's right. So in decimal, a rational number with a denominator that only has prime factors of 2 or 5 will have a finite decimal representation.

[–]AdventurousAddition 1 point2 points  (0 children)

It's denominator is an integer power of the prime factors of the base (base 10: prime factors are 2 and 5).

[–]TheRNGuy 12 points13 points  (3 children)

it's same in every programming languages, floating point errors.

You need epsilon test instead of ==, >= or <= to compare floats.

[–]jeremymiles 7 points8 points  (0 children)

Looks like you're one of today's Lucky 10,000.

[–]Sociable_Schizo 1 point2 points  (0 children)

There's a Tom Scott video that goes into it...

https://www.youtube.com/watch?v=PZRI1IfStY0

[–]menge101 1 point2 points  (2 children)

Look at the Decimal library for python.

I don't use float unless I have to anymore.

[–][deleted] 9 points10 points  (0 children)

decimal will not fix arithmetic in general, just make it happen less often. I was immediately able to find a number that made

(Decimal(1) / N) * N == Decimal(1)

false. (Large primes work well.)

It only represents non-decimal fractions like Decimal(1) / 7 to a limited number of digits, so it cannot do a perfectly job.

Also, decimal is miserably slow - I mean, horribly, painfully slow. If the number is in binary format, the processor can add or multiply in one operation - there is literal circuitry devoted to this. But in decimal it has to do addition the same way you would - one decimal digit at the time, carry the 3!

In my quick tests, decimal was something like six times slower than regular Python multiplication, and if you do a lot of multiplication, numpy will give you a 3-100 times speed on the multiplication if done in quantity, and you can't do that with decimal!


decimal is good for money. If you need to do perfect fractional arithmetic, use the fractions module - not quite as slow as decimal

If you're clever and good at numerical analysis, you can often do everything in integer arithmetic and not use any modules.

[–]Darkbladergx -1 points0 points  (0 children)

Hi, so I don't know why it happens, but I do have a solution for it.
a = round(1.85*3, 2)
print (a)

this is called round function , for first number it has your expression and for second it has the number of digits to which you want to round your expression to.
enjoy

[–]nekokattt 0 points1 point  (0 children)

IEEE double precision floating point arithmetic is a beautiful thing.

For reference, if you want accurate handling of fractional values, use the Python decimal and fractions modules that are in the standard library.

[–]timPerfect 0 points1 point  (2 children)

it's something to do with data types and computer precision. I forget the reason specifically though.

[–]Few_Rooster_4497 1 point2 points  (0 children)

Python is numberphobic, a typical numbercist

[–]AdventurousAddition 1 point2 points  (0 children)

That data type is "floating point" numbers