> That Go uses arbitrary-precision for constant expressions seems dangerous to me.
I'm somewhat baffled by this statement. If a Go program compared a constant expression float against a runtime computed float, it could have unexpected results, but comparing floats in general is dangerous. I don't see how this language quirk increases that danger in a meaningful way.
It's not an issue an issue about whether or not efficient float formats are dangerous to work with rather one of whether they are consistent to work with in a language. It's simple enough to remember when reading about it but will you and everyone else know this from the start and remember it every time it comes up without fail?
That said, for the negatives it comes with it does come with positives as well and I think that makes it worth it.
It's one thing if a language outputs one, two, or zero for this expression. Two and zero are mathematically wrong, but at least they're predictable.
It's another thing if a language sometimes outputs two and sometimes outputs one depending on the syntax of the request. This is reasonable where that syntax change is not an explicit cast to double precision or single precision, but dangerous if it uses a behind-the-scenes default of arbitrary precision in compiled literals and a default of FP32 in implicitly typed literal assignments.
Floating point is all about relative precision. f64 gives you 53 bits of mantissa, so you have precision of about 1.1e-16, almost 16 decimal places. But the 17th place, and sometimes the 16th place, gets clobbered.
Integers have absolute precision, at the expense of either range (say, i64) or arbitrarily growing size.
> What's really surprising is that this number is only ~16 million in single precision floats.
What does this mean? Surely even single precision floating point can represent a number far closer to the original than 16 million? Edit: It appears the closest number in single precision is 9007199000000000.0
Edit2: Oh I see: you mean that the first number that cannot be represented precisely by a single-precision float is ~16 million.
bc -l is my standard command-line tool for arbitrary precision calculations (-l not needed here but handy for functions like sqrt(), e(), and log l() ):
$ bc -l
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software
Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
9999999999999999.0 - 9999999999999998.0
1.0
I got tired of typing bc -l years ago, I have a function so I can do it on the command line: calc 2^27 + 3^15. I wish somehow the shell would let me add parens here, instead of having to escape them with a quote.
function calc2
{
if [ $# -eq 0 ];
then echo 'pass commands for math evaluation, like calc l(2^32/17) + 3';
fi;
echo $* | bc -l;
}
I guess old unix persons do use bc. But even older unix persons, like you and me, use dc. Oh, and I still have my trusty old HP48GX calculator that I bought nearly thirty years ago. Algebraic notation is fine for paper, bur RPN is best for calculating. I am glad I can even have an RPN calculator (PCalc) on my phone now.
rpn for for the win I guess ;-). I actually didn't get to try my first linux stuff until grad school in the 80s. I wish I had saved off all my init files from then to see what they look like now.
Back in the day (Unix Seventh Edition) bc was just a front end that compiled the expression and piped it to dc. My take it is most people didn't find rpn a win, and bc was the fix.
I used bc's compiled output ("bc -c") to learn how to make dc jump through hoops.
Floating-point literals are confusing. The basic arithmetic less so (although it too has some pitfalls). I said this before, but I think inexact literals (which are very common) should cause warnings in languages where used.
>>> 9999999999999999.0 == 10000000000000000.0
True
>>> from decimal import Decimal as D
>>> D(9999999999999999.0)
Decimal('10000000000000000')
"It's a simple question" I find it very much not simple question; indeed I'm not sure what is the expected "right answer" here?
Emacs Calculator with high precision will get you what you need (`M-x calc RET P 20 RET 9999999999999999.0 RET 9999999999999998.0 RET -` gives you `1.`)
Definitely keeping this example in my back pocket for explaining to people why floating point is not what you think it is.
I take issue with this. Drift from floating point inaccuracies can compound quickly and dramatically affect results. Sure if you're just looping over a 1000 item list, it's not going to matter that JavaScript is representing that as a float/double, but in a wide variety of contexts, such as anything to do with money, it absolutely does matter.
Another example is open world games. They have to keep world coordinates centered on the player because for large worlds, the floating point inaccuracy in the far reaches of the world starts to really matter. An example of a game that does this is Outer Wilds.
caveat: this is not my direct experience so i might be wrong -- but someone who was doing an different masters project at the same time as mine was doing a mini on-rails video game and mentioned this.
apparently it's also because of the "what is up?" question.
e.g. in outer wilds ... how do you determine which way is "up" when "up" for the player can be any direction.
You say that, and then an army of idiots out in the real world continues to use floats for financial data and other large integers.
I ran into a site that broke because they were using 64b unix nanotime in Javascript and comparing values which were truncated. You see this in js, python, etc. constantly.
For the JS case, that's really JavaScript's fault, since double-precision float ("number") is the only built in numeric type, other that BigInt, which has only existed for a few years.
Not just floating point, but 64-bit IEEE 754 specifically. The last few bits of the mantissa are not sufficient to represent the last decimal digit exactly. 80 bits would suffice for this particular example, but would fail similarly with a longer mantissa.
BTW this is one of the reasons why you should never represent money as a float, except when making a rough estimate. Another, bigger reason is that 0.1 is an infinite repeating fraction in binary, so it can't be represented exactly.
I encounter bugs around this semi-rarely, but most of my career has been building tools around data analytics. While it's rare that I encounter bugs tied to floating point it's frequent that I need to be aware of floating point math and if a float will be acceptable here. The rareness of the bug has more to do with it being a rookie mistake that won't make it past code review than "it doesn't matter" as the comment implies.
The next representable number after 9999999999999998.0 is 1.0e16. 9999999999999999.0 is not exactly representable in IEEE 754 floats, and will be rounded up or down.
The difference between 0.0 and 2.0 in the table is likely due to different rounding modes. I'm curious how different languages end up with different rounding modes. Is that possible to configure?
It's not different rounding modes, but different floating point formats. Google is doing something very weird that is also base 10 related. Not sure about TCL.
TCL does for quite some years now use real integers (unbounded size) and floats (float8) whenever possible. By the way, the article does not specify versions of languages, so:
with Ada.Text_IO; use Ada.Text_IO;
procedure Example is
begin
Put_Line (Float (9999999999999999.0-9999999999999998.0)'Image);
end Example;
Result:
1.00000E+00
If I really wanted to, I could use a decimal instead, e.g.
with Ada.Text_IO; use Ada.Text_IO;
procedure Example is
type Decimal is delta 1.0E-20 digits 38;
begin
Put_Line (Decimal (9999999999999999.0-9999999999999998.0)'Image);
end Example;
The `real` type (float32) only has 24 bits of precision. So converting `9999999999999999.0` or `10000000000000000` or even `10000000300000000` yields the 32-bit float `10000000272564224`.
For some reason Postgres prints it as `10000000300000000`. It uses a heuristic to print a "pretty" number that converts to the actual stored value, and it's not smart enough to give `10000000000000000`. Some heuristic like this is needed so something like `0.3` doesn't print the actual stored value of `0.300000011920928955078125`, which would be confusing.
The page never explicitly states what the right answer is, but based on the output of their suggested "correct" perl, we can infer that they expect 1.
This is just me being idiosyncratic I guess, but if I see a number with a decimal place, I default to interpreting it as an fp64 unless otherwise specified - which yields a "correct" answer of 2.0 (which is not an answer I can get to in my head, admittedly)
If the question-asker wanted integer arithmetic, they'd have left off the ".0", and if they wanted something other than fp64 roundTiesToEven they should've been more explicit :P
The page is asking which language answers the math question correctly, not which one implements a particular standard correctly. Perl6 also has a correct implementation, it's just using a different standard than the other languages.
I agree that what we call "math" is just a set of rules that could be defined differently, but a standard like IEEE-754 is downstream of math, and is explicitly defined as a method to perform arithmetic (which is part of math).
So they aren't on the same level, IEEE-754 is not an alternative standard to math. The answers can all be considered correct by a certain definition, but they are not equally correct in our shared context as human beings who know what math is.
Math is not a singular set of rules, it depends on who you ask, and what the context is. We have a pretty decent shared understanding, but it's not perfectly uniform.
59 + 1 can very reasonably be 1:00, for example.
I'm not saying 2.0 is the correct answer, I'm just saying it's not any less correct than 1 is.
I wasn't surprised, but that is not what I would expect if I were to see that expression. Two reasons for that: even though there is a decimal, I still mentally parsed it as an integer, and I wouldn't be thnking of the precision of the value.
(That said, I would never enter an integer with a decimal, and I would think of precision if it was a float. That said that said, there are other ways to bump into that problem which wouldn't make it so obvious - such as dealing with inputs from a user.)
I was curious about `soup`, clicking on it, the author states:
>soup is a programming language I've been working on for a few years that is designed to make it easier for the programmer to write fast, robust programs with no bugs.
>Availability
>I'm sorry to say soup is not yet available for general use.
The result of subtracting 9999999999999998.0 from 9999999999999999.0 is 2.0. This result is due to the limitations in the precision of floating-point arithmetic in computers. When dealing with very large numbers, the precision can be lost, leading to such unexpected results.
these days it looks like ChatGPT generates and runs code behind the scenes for math questions (it used to be pretty bad at these kinds of math questions. you can click the little "view analysis" button to see the code it's running.
when I ask about this question it generates and runs Python behind the scenes, thus the incorrect answer.
Although it offers an explanation, which is something it could not do by just evaluating the expression. So the explanation is in its training set. And given that this is an understood flaw or limitation, it is possible it didn't even need to run any code.
ChatGPT 4 pondered for a while, then started "Analyzing..", and eventually "Finished analyzing" (all following verbatim output):
python 9999999999999999.0 - 9999999999999998.0
Result 2.0
The result of the calculation 9999999999999999.0−9999999999999998.0 is 2.0.
This result may seem counterintuitive, but it's due to how floating-point numbers are represented and handled in computing. Floating-point arithmetic can introduce small rounding errors, especially with very large numbers, as precision is limited. In this case, the precision required to represent these large numbers exactly is beyond what typical floating-point representations (like IEEE 754 double precision) can handle, leading to a slight error in the calculation.
No kidding. I don't trust my own by hand calculations anymore. And why bother? I'd rather just write down the equation I need and let a computer do the rest.
Almost every day for my 5+ years of college, and fairly regularly since (although I encounter the types of complex math problems it excels at far less frequently today). I think there are many others like myself who use it.
any time you want to do a bunch of unit conversions. I was doing some napkin math comparing Tadej Pogačar on a 1200 W e-bike to him on a gas dirtbike (gas dirtbike smokes e-bikes, btw) and it really helped with all the weights and powers being specified in different units between the two. Not just one conversion but when there are a bunch of steps that can introduce errors.
I'm somewhat baffled by this statement. If a Go program compared a constant expression float against a runtime computed float, it could have unexpected results, but comparing floats in general is dangerous. I don't see how this language quirk increases that danger in a meaningful way.