I've been writing python from the last century and this year is the first time I'm writing production quality python code, everything up to this point has been first cut prototypes or utility scripts.
The real reason why it has stuck to me while others came and went is because of the REPL-first attitude.
A question like
>>> 0.2 + 0.1 > 0.3
True
is much harder to demonstrate in other languages.The REPL isn't just for the code you typed out, it does allow you to import and run your lib functions locally to verify a question you have.
It is not without its craziness with decorators, fancy inheritance[1] or operator precedence[2], but you don't have to use it if you don't want to.
[1] - __subclasshook__ is crazy, right?
[2] - you can abuse __ror__ like this https://notmysock.org/blog/hacks/pypes
Welcome to Rakudo™ v2025.06.1.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2025.06.
To exit type 'exit' or '^D'
[0] > 0.1 + 0.2 > 0.3
False 1e-1 + 2e-1 > 3e-1
Which will evaluate to True.a scientist knows that 0.1 + 0.2 is not greater than 0.3, only a computer geek would think that this is OK
The REPL example intends to show what the program does, not whether or not something is intuitive for you.
Second, using your same argumentation,
>>> 010 + 006 == 14
True
Is also wrong.It's based on a misunderstanding of what representations of numbers in programming languages are.
In Python (and almost all other languages), 0.1 means the IEEE float closest to the decimal number 0.1, and arithmetic operations are performed according to the IEEE standard.
I am making the point that using a decimal literal (eg 0.1) representation for a IEEE double is a bad choice and that using it as a representation for a Rat (aka Fraction) is a better choice.
I 100% accept your point that in Python 0.1+0.2>0.3 is true which is why I prefer Raku’s number system.
Happy to see Raku getting some press.
https://docs.python.org/3/tutorial/floatingpoint.html#floati...
Note that "On overflow of the denominator during an arithmetic operation a Num (floating-point number) is returned instead." A Num is an IEEE 754 float64 ("On most platforms" says https://docs.raku.org/type/Num)
Python always uses IEEE 754 float64, also on most platforms. (I don't know of any Python implementation was does otherwise.) If you want rationals you need the fractions module.
>>> from fractions import Fraction as F
>>> F("0.1") + F("0.2") == F("0.3")
True
>>> 0.1 + 0.2 == 0.3
False
This corresponds to Raku's FatRat, https://docs.raku.org/type/FatRat. ("unlike Rat, FatRat arithmetics do not fall back Num at some point, there is a risk that repeated arithmetic operations generate pathologically large numerators and denominators")that said, decimals (eg 0.1) are in fact fractions, and the subtlety that 0.1 decimal cannot be precisely represented by a binary floating point number in the FPU is ignored by most languages where the core math is either integer or P754
bringing Rational numbers in as a first class citizen is a nice touch for mathematicians, scientists and so on
another way to look at it for Raku is that
Int → integers (ℤ)
Rat → rationals (ℚ)
Num → reals (ℝ)"0.1" is what the language specification says it is, and I disagree with the view that it's ignored by most languages when it's often clearly and explicitly stated.
That most people don't know IEEE 754 floats, and do things like store currency as floats, is a different matter. (For that matter, currency should be stored as decimal, because account rules can be very particular about how rounding is carried out.)
Similarly, 3 * 4 + 5 may 'in fact' be 17 .. sometimes. But it's 27 with right-to-left precedence ... and 19683 in APL where * means power (3 to the power of 9). While 3 + 4 * 5 may be 35 or 23 (or 1027 in APL).
FWIW, FatRat is ℚ, not Rat. Rat switches to Num if the denominator is too high, as I quoted.
Bringing it back to Python, ABC (which influenced Python's development) used a ratio/fraction/FatRat natively, which handled the 0.1 + 0.2 == 0.3 issue, but ran into the 'pathologically large numerators and denominators' problem even for beginning students.
I see Rat as a way to get the best of both worlds, but I'm certain it has its own odd edge cases, like I suspect x + 1/13 - 1/13 might not be the original value if x + 1/13 caused a Rat to Num conversion.
true, in fact the syntax of Python consumes the literal '0.1' as a double [float64] ... so ok maybe I was a bit strong that my fact trumps the Python fact (but it still feels wrong to say that 0.1 + 0.2 > 0.3)
---
I welcome your correction on FatRat ... btw I have just upgraded https://raku.land/zef:librasteve/Physics::Unit to FatRat. FatRat is a very useful string to the bow and imo cool that it's a core numeric type.
See also https://raku.land/zef:librasteve/FatRatStr as my path to sidestep P754 literals.
---
We are on the same page that the Rat compromise (degrade to P754) is optimal.
---
As you probably know, but I repeat here for others, Raku has the notion of https://docs.raku.org/language/numerics#Numeric_infectiousne... which means that `x + 1/3' will return a Rat if x is an Int or a Num if x is a Num. All "table" operators - sin , cos, log and so on are assumed to return irrationals (Num).
Python is a fancy calculator.
To be clear, while in the mathematical sense, yes, sin, cos, and log generally return irrationals, in their IEEE 754 forms they return an exact value within 1 ulp or so of that irrational number. Num is a rational. ;)
>>> x=5**0.5
>>> x
2.23606797749979
>>> x.as_integer_ratio()
(629397181890197, 281474976710656)
Scheme uses the phrase "numerical tower" for the same sort of implicit coercion.I have some test Rust code where I add up about a hundred million 32-bit floating point numbers in the naive way, and it takes maybe a hundred milliseconds, and then I do the same but accumulating in a realistic::Real because hey how much slower is this type than a floating point number, well that's closer to twenty seconds.
But if I ask Python to do this, Python takes about twenty seconds anyway, and yet it's using floating point arithmetic so it gets the sum wrong, whereas realistic::Real doesn't because it's storing the exact values.
Not really. It's a limitation of the IEEE floating point format used in most programming languages. Some numbers that look nice in base 10 don't have an exact representation in base 2.
1/3 doesn't have an exact representation in base 10 or base 2. 1/5th does have an exact representation in base 10 (0.2), but doesn't in base 2. 1/4th has an exact representation in base 10 (0.25) and in base 2 (0.01)
Really?
$ python -m timeit -s 'import random; x = [random.random() for _ in range(100000000)]' 'sum(x)'
1 loop, best of 5: 806 msec per loop
This is on 11-year-old hardware. Even the generation isn't that slow: $ time python -c 'import random; x = [random.random() for _ in range(100000000)]'
real 0m10.942s
user 0m9.590s
sys 0m1.346s
Of course, Python forces the use of doubles internally. Any optimizations inherent to 32-bit floats are simply not available.If you did
total = 0.0
for value in data:
total += value
instead of total = sum(data)
then yes, the answer will take longer and be less accurate. But the naive native Rust equivalent will be less accurate than Python's sum(data).That's entirely correct, you shouldn't do this. And yet people do for one reason and another. I'm aware of Kahan summation (and Neumaier's improvement), but it wasn't the point of the benchmarks I happened to be performing when this topic arrived.
You will not be surprised to learn there's a Kahan adaptor crate for Rust's iterator, so (with that crate) you can ask for the Kahan sum of some floating point iterator just the same way as you could ask for the naive sum. I suppose it's conceivable that one day Rust will choose to ship a specialisation in the stdlib which uses Kahan (as Python did in newer versions) but that seems unlikely because it is slower and you could explicitly ask for it already if you need it.
You don't like Python's use of IEEE 754 float64 for its "float" type because it's already so slow that you think Python should use a data type which better fits the expectations of primary school math training.
Then to demonstrate the timing issue you give an example where you ignore the simplest, fastest, and most accurate Python solution, using a built-in function which would likely be more accurate than what's available in stock Rust.
If accuracy is important for the first, why is it not important for the second?
Are you aware of the long history of compiled versions of Python (PyPy, numba, and more), plus variants like Cython, where the result has near Rust performance levels?
Were the core float be a non-IEEE 754, those compiled versions would either be dog slow (to be compatible with Python's core float) or give results which are different than CPython's.
Even if they did not exist, there would be a lot of questions about why a given program in C, Rust, Pascal, or any other float64-based system, gives different answers when translated to Python.
FWIW, I, like others, could not reproduce your 20 seconds timing. Python is indeed slow for this sort of task, but even for explicit iteration my code is an order of magnitude faster than you reported.
I was not aware of Cython, which sounds like a terrible idea but each to his own nor Numba, though I have worked with PyPy in the past. I'm afraid that the idea that somehow every Python implementation would be compatible caused me to choke. Python doesn't really care about compatibility, behaviour of the sum function you brought up was changed twice since Python 3.
This is an old machine, so I can well believe you can do the iteration faster. My initial interest happened because by total coincidence I was writing benchmarks for realistic which try out f32 and f64 vs realistic::Real for various arithmetic operations, and so I wondered well, isn't even Python much faster and (with my naive iteration) it was not.
As you are hopefully aware, new programmers are equally likely to run into languages where the default is the 32-bit IEEE floating point and so 1.0 + 2.0 > 3.0 is false for them as they are to encounter a language like Python with 64-bit IEEE floats. I'd expect, as with Python's experience with their hash tables, the kind of people writing Python as their main or even only language would always be pleased to have simpler, less surprising behaviour, and the rationals are much simpler - they're just slower.
Guido van Rossum, who started and lead the Python project for many years, previously worked with ABC, which used rational as the default type. In practice this caused problems as it was all to easy to end up with "pathologically large numerators and denominators" (quoting https://docs.raku.org/type/FatRat). That experience guided him to reject rationals as the default integer type.
Pathologically large numerators and denominators make rationals not "just slower" but "a lot slower".
> somehow every Python implementation would be compatible
It's more of a rough consensus thing than full compatibility.
> Python doesn't really care about compatibility
Correct, and it causes me pain every years. But do note that historic compatibility is different than cross-implementation compatibility, since there is a strong incentive for other implementations to do a good job of staying CPython compatible.
FWIW, the laptop where I did my timings is 5 years old.
The new programmers in my field generally have Python as their first language, and don't have experience with float32.
I also know that float32 isn't enough to distinguish rationals I need to deal with, since in float32, 2094/4097 == 2117/4142 == 0.511106, while in float64 those ratios are not equal, as 0.5111056870881132 != 0.5111057460164172.
(I internally use uint16_t for the ratios, but have to turn them into doubles for use by other tools.)
The PC I tried this is on is about 10 year old, I bought this place in 2014 and the PC was not long after that.
sum() of a list with 100M floats took 0.65 seconds. The explicit loop took 1.5 seconds on CPython 3.13.
But again, yes, Rust performance runs rings around CPython, but that's not enough to justify switching to an alternative numeric type given the many negatives, and Python's performance isn't as dire as you suggest.
True
That is false. What is it that is "...is much harder to demonstrate in other languages?I am missing something
Welcome to Rakudo™ v2025.06.1.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2025.06.
To exit type 'exit' or '^D'
[0] > 0.1 + 0.2 > 0.3
FalseWhat's false about it? That is the result if you're using IEEE floating point arithmetic.
But, where did we say that's what we want? As we've seen it's not the default in many languages and it isn't mandatory in Python, it's a choice, and the usual argument for that choice would be "it's fast" except, Python is slow, so what gives ?
But in answer to “where did we say that's what we want?” I would say, as soon as we wrote the expression, because we read a book about how the language works before we tried to use it. Αfter, for example reading a book¹ about Julia, we know that 0.1 + 0.2 will give us something slightly larger than 0.3, and we also know that we can type 1//10 + 2//10 to get 3//10.
I'm comfortable with that rationale in proportion to how much I believe the programmer read such a book.
I haven't taken the course we teach say, Chemists, I should maybe go audit that, but I would not be surprised if either it never explains this, or the explanation is very hand-wavy, something about it not being exact, maybe invoking old fashioned digital calculators.
The amusing thing is when you try to explain this sort of thing with a calculator, and you try a modern calculator, it is much more effective at this than you expected or remember from a 1980s Casio. The calculator in your modern say, Android phone, knows all the stuff you were shown in school, it isn't doing IEEE floating point arithmetic because that's only faster and you're a human using a calculator so "faster" in computer terms isn't important and it has prioritized being correct instead so that pedants stop filing bugs.
Using floating-point in Python is still much faster than using an exact type in Python.
$ # At this scale, we need to be aware of and account for the timing overhead
$ python -m timeit 'pass'
50000000 loops, best of 5: 8.18 nsec per loop
$ # The variable assignment defeats constant folding in the very primitive optimizer
$ python -m timeit --setup 'x = 0.1; y = 0.2' 'x + y'
10000000 loops, best of 5: 21.2 nsec per loop
$ python -m timeit --setup 'from decimal import Decimal as d; x = d("0.1"); y = d("0.2")' 'x + y'
5000000 loops, best of 5: 62.9 nsec per loop
$ python -m timeit --setup 'from fractions import Fraction as f; x = f(1, 10); y = f(2, 10)' 'x + y'
500000 loops, best of 5: 755 nsec per loopMathematics. IEEE floating point arithmetic gets it wrong!
Another example of why we should use integer algorithms where ever possible.
Swift will also return true unless you specify the type. Though, I suppose that is the key difference -- proper typing.
shagie@MacM1 ~ % docker run -it openjdk:latest jshell
Unable to find image 'openjdk:latest' locally
latest: Pulling from library/openjdk
...
Status: Downloaded newer image for openjdk:latest
Oct 01, 2025 6:23:46 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
| Welcome to JShell -- Version 18.0.2.1
| For an introduction type: /help intro
jshell> 0.1 + 0.2 > 0.3
$1 ==> true
jshell>
This has been around since JDK 9. https://docs.oracle.com/en/java/javase/17/jshell/introductio...That said, changing how you think about programming... even with jshell I still think Java in classes and methods (and trying to pull in larger frameworks is not as trivial as java.lang packages). However, I think Groovy (and a good bit of Scala) in a script writing style.
jshell itself is likely more useful for teaching than for development - especially once you've got a sufficiently complex project and the ide integration becomes more valuable than the immediate feedback.
Still, something to play with and one of the lesser known features of Java.
Even if it's less secure, the beauty of Go, a single, static binary impervious to version changes, is very appealing for small projects you don't want to keep returning to for housekeeping.