Why does 0.1 and 0.2 = 0.30000000000000004?
jvns.ca
jvns.ca
2.03 - 2 - 0.03 = 0
The vast majority of data transformation and BI tools (Power BI, PowerQuery, Tableau, etc.) return FALSE for this expression: 0.1 + 0.2 = 0.3
That's because they use floats instead of decimals and that introduces subtle errors in data. These errors usually never get noticed because everyone doesn't expect errors in basic math. It's a mystery to me why most commercial software intended for business and financial calculations don't use fixed point decimals. My post about this: https://www.linkedin.com/feed/update/urn:li:activity:7028101...PS. If you design software that works with money amounts, always use fixed point decimals. Don't use floats, it's just wrong!
Eh, doubles are fine for (most) currencies. Just don't do comparisons without appropriate epsilons.
People who compoare floating-point numbers for equality are going to make other fundamental mistakes with whatever data type you force them to work with.
You should have seen my face when debugging this.
Here's what I'm seeing:
---
JavaScript
(function() {
let inc = 0.37;
let times = 5000;
let total = 0;
for (let i = 0; i < 5000; i++) {
total += inc;
}
console.log('expected:', inc \* times);
console.log('actual:', total);
})();
expected: 1850actual: 1849.9999999997679
---
Java
public class MyClass {
public static void main(String args[]) {
double inc = 0.37;
double times = 5000;
double total = 0;
for (double i = 0; i < 5000; i++) {
total += inc;
}
System.out.println("expected: " + (inc \* times));
System.out.println("actual: " + total);
}
}
expected: 1850.0actual: 1849.9999999997679
---
update: Changing `double` to `float` in Java yields:
expected: 1850.0
actual: 1849.9778
and maybe that lines up with what you meant by "the differences should be a LOT farther than 3 digits", though it's hard to tell what you mean by "differences on the order of 10-20".
declare @i float =0.37,@times int = 0,@total float = 0
while @times < 5000
begin
set @total+= @i
set @times +=1
end
select @total
1849.99999999977or
select sum(t) from(
select top 5000 convert(float, 0.37) t
from sysobjects a cross join sysobjects b) z
1849.99999999977Do the sum two ways:
- export them in excel and do a sum
- select sum(value) from t
and you get two different values, with a difference of something like 15.
Replace double(10,2) with decimal(10,2), and the sum(value) works correctly.
I still have no idea why this happens. I could expect a tiny difference, and code like your above will indeed give differences like this, well under 1.0, but my particular scenario had differences many orders of magnitude higher. Still can't explain.
Oh, that's something entirely different. If some of your other numbers are much larger than 0.37, you will run into a different kind of problem.
You know about exponent and mantissa? Adding a value with a small exponent to a value with a big exponent will cause imprecisions. In extreme cases the result is just the bigger number.
But actually, since fixed point decimals are not all ways available use the smallest unit of account, not smallest legal tender, and integer arithmetic
And learn how to round
If you really don’t have access to a decimal type I think the best solution is to convert it to micro-units (price * 10^6). This is what Android does in its billing library.
What people use as cash is a distraction, generally
1. Decimal math
2. Binary floating point math
3. Domain knowledge trumps all; it makes no difference
The cardinal sin isn't using doubles for currency; it's using them without understanding either the tool or the job that you're asking the tool to perform.
I guess there are some contexts where floats are fine for monetary amounts, for example if you make economic forecasts or simulations. As long as the amounts are not real-world transactions, floats are probably fine.
Then that's your epsilon. Or, more reasonably, one or two powers of 10 further down. Absolutely nobody cares about a millionth of a cent. A few people care about a thousandth, though. Most people get antsy if you can't pin a calculation down to the nearest cent.
Floats are pretty much never fine for currency, but doubles are usually OK. There's a world of difference between a 24-bit mantissa and a 53-bit mantissa.
This is exactly what the article describes and explains. 10 cent plus 20 cent does not equal 30 cent, when using binary floating point. This is not acceptable in accounting, since at one point the error may accumulate and cause an error at the size of a cent (or more).
Years ago, I had a few apps in the App Store that made extensive use of it. It really was very nice to work with.
(I guess it depends on what you mean by "true" decimal. If you meant BigNum, then sure.)
Only floating-point decimal is hairy. Fixed-point decimal is hardly more difficult than integer math.
That's because the binary is implemented in hardware for you; and those days you can usually (but not always) trust the hardware to do the correct thing; if you have to implement it youself (say, deterministic, side-effect free math), it's also hairy.
Don’t write with such certainty! Decimal math is great advice for many/most situations, but what if you have a LOT of numbers and not a lot of time? That big number crunching GPU is not available if you take this approach.
Numerical Methods was the most difficult CS course I took in university and also the one I did the worst in. And of course it was an elective, or else they wouldn’t have graduated many people at all. If you’re doing a lot of number crunching stuff, maybe you should ask people that know how to number crunch to design your system so it has the smallest errors :)
PS - I’m not the person for that job!
Just getting the data to the GPU for a compute shader to run on it would take longer than just doing on the CPU in almost every case.
On the other hand the CPU has floating point math too, and floating point math is MUCH faster than decimal math.
So his point holds if you replace GPU with CPU, but his use of GPU is likely inaccurate.
Anecdote: In state of the art physics code, it's common to use integers for coordinates [1]. Recently, I was working on a BSc. toy project, and I used ints for coordinates as is common. With both ints and floats it's important to be careful around functions like `tan` that go to infinity, but of course floats are more forgiving, so I prototyped some of the code using doubles.
I ended up comparing performance and it wasn't even funny. Double precision arithmetic was anywhere between 3x (where a good int algorithm is known) all the way to 100x faster (if the int algorithm is cordic, for example) than integers.
1: Springel 2013 p. 1-82 https://wwwmpa.mpa-garching.mpg.de/gadget4/gadget4-code-pape...
I'm not sure when I read it, but long ago I read some kind of AMA from a MS engineer working on Excel saying that his greatest achievement was working on the team that made the Excel's DAG solver trivially parallelizable. In the same thread, he mentioned that offloading to the GPU was being looked into. I guess it never came to fruition.
The number 1 supercomputer in the world has 8 million GPU cores (or is it 8 million GPUs? Execution Units?) and 600k CPU cores.
They are doing floating point math on GPUs. Numerical analysis [1] is used to create the best accuracy possible. This is something that has been done for thousands of years, basically since math has had problems with no "exact" solution.
Boy, did I file a pissy bug with the Sheets team that day, and then requested an Excel install I never let go of since that day.
* I guess it's maybe not surprising to js developers, but don't most modern browsers have integers by now?
What numbers in javascript are not actually doubles (what js uses under the hood for every number)?
You can convert them from doubles to 32-ints apparently by some bitwise hacks like (I'm pretty sure they still are 'doubles' though - and it just does some rounding tricks)
``` |0 //signed >>>0 //unsigned ```
So any webapp (google sheets) will likely have problems related to floating point math.
You can use the native BigInt arbitrary-precision integers and ignore all "fits under this arbitrary limited bit slice" problems.
You can use doubles as a integer directly, in a safe way, until you touch the 53 bits barrier (Number.MIN_SAFE_INTEGER === -9007199254740991 or Number.MAX_SAFE_INTEGER === 9007199254740991).
None of those really are (modern) webapp issues.
Floats have varying precision, not uniform. The closer the number is to zero, the more precision it has. There's a point where the precision is so low that it only covers whole numbers, and then a point after that where the precision is less than whole numbers.
Given the context of pointers, it's quite possible that they were large enough to reach that less-than-whole-number-precision range.
EDIT: For 32-bit floats, that's apparently any number above 16,777,216. Which seems surprisingly small to me. For 64-bit doubles that goes to 9,007,199,254,740,992.
https://blog.demofox.org/2017/11/21/floating-point-precision
Anyway, yes, that's all true. Excel, Sheets, JavaScript numbers, Java doubles, Python floats, and so on all work this way. That's why I asked how switching spreadsheet implementations solved the problem.
I agree with you that switching programs that are all using the IEEE floating-point formats with the same underlying hardware implementations should result in the same answers.
But Excel also uses floats (as in, Single-precision floating-point format, aka float32), because of "compatibility with Lotus 1-2-3".
https://softwarerecs.stackexchange.com/questions/53292/any-s...
https://learn.microsoft.com/en-us/office/troubleshoot/excel/...
[1] https://people.eecs.berkeley.edu/~wkahan/Mindless.pdf, §2
I find it funny the gap between what computer people think finance requires and actual practice.
The tax people in the US generally aren't interested in pennies any more! And when you use tax software that throws away the pennies before the final results, then your sums may very well not match the forms whose information is independently reported to the IRS, by well over a dollar. But nobody cares!
...while financial institutions send reports to clients and the IRS that have the totals rounded only at the end, meaning the things that are supposed to match don't and can't.
Even though they are both in dollars, they are not consistent even to the nearest dollar.
You do not want your numbers changing in the next version of Excel.
But there’s nothing stopping you from doing arbitrary precision decimal in excel except back compatibility (and all the thousands of lines of code looking for a float).
Fundamental reason being that powers of 1/10 in decimal fractions are not always representable by powers of 1/2 in binary. Specifically 0.1 = 0b00011001100110011... is infinitely long binary string. Truncating it to any finite bit-width like 32 or 64 bits always introduces an error.
The correct answer is to use floating point and to understand it and numerical software before doing it. If you don't have a decent understanding of numerical analysis, don't write important numerical software.
So yes, if you use fixed point you obviously get different results, but you won't get correct results according to what's required by accounting standards unless you truncate to fixed point when needed. It's not that this difference is large - after all, it's about rounding off fractions of a cent - but accounting does need to be exact.
Completely depends - package them in tranches to sell to secondary markets by the thousands - then you do exactly as I pointed out. Or if you're doing Monte Carlo futures projections modeled as compound interest and only need the value at a future time.... Or any of thousands of financial modeling needs....
If you're printing monthly bills for consumers then you round, but only at output, and only for viewable parts.
So you cannot claim things are not computed this way - it depends on the financial application you're working on,
>So yes, if you use fixed point you obviously get different results, but you won't get correct results according to what's required by accounting standards
Ha - which standard are these? Care to cite them? I've been through this space a long time, and every time someone tells me there is a standard and I ask them for it they soon realize there is no gold, single standard. There are zillions of acceptable choices. There are ones for consumers, ones for intrabank, interbank, fed to bank, loans, mortgagaes, taxes, and on and on. There is no "correct results according to what's required by accounting standards ".
Please cite your standards that apply to all these cases.
Have you worked in finance on numerical financial software?
>you round to a fixed point (e.g. whole cents) and that's it, that's the final truth
Having done numerical stuff, including finance for decades, you simply write the entire codebase in floating point, being sure to do proper analysis that things handle ranges correctly.
Then, and only for output, do you snap to desired observable precision. Never ever even once do you round something to make it look pretty, then jam it back into calculations.
It doesn't matter if the interest is calculated for consumers, intrabank, interbank, fed to bank, loans, mortgages, taxes - the interest rates and interest periods and interest day basis and all kinds of details may vary, and of course accounting standards vary between countries, but the core principles are the same that it all eventually comes up to some amount of money owed to the counterparty - measured in whole cents or perhaps whole dollars or roubles or whatever, but never an arbitrary-precision float. You can't get to a final compounded result "only for output" because when interest compounds (for example, monthly) then at every such point you do have actual "intermediate output" which materializes into a customer-visible change of balance from which the next period's interest is then calculated, and that intermediate output gets rounded because that (unlike estimates or models) is a specific balance owed and it is denominated in a currency with fixed, limited precision. And so after many such steps, the total actual compound interest - i.e. the actual dollars and cents paid by (or to) the counterparty - is slightly different from what the common modeling approach gets if e.g., as you state, it does rounding only for the final output. The difference is tiny, so there is no problem for modeling to ignore it, but actual financial systems (i.e. tracking facts of money owed, not doing estimates and models for decision support) do have to come down to a rounded fixed-precision number owed for every contract at the end of every day.
var t = obj.x // fixed -> fp
for (<n times>) {
t += 0.1
}
obj.x = t // adds exactly n/10
The key observation is that intermediates don’t float free long enough before being assigned back to a fixed storage. So the error has no chance to accumulate. But still can manifest in comparisons. If necessary, floating point can be replaced with precise enough fixed point for intermediates (at a cost, e.g. tens of digits).The correct answer is to use floating point and to understand it and numerical software before doing it
Yes, but this also has associated costs and risks. Unless you’re pressed against some wall, it’s more safe to offload that to a runtime. Humans are way too unreliable when it comes to understanding numerical software.
This is why ad-hoc methods that people feel are ok based on untrained or not-carefully-studied analysis should not write production numerical code.
Just use floating point everywhere, and only output things snapped to whatever resolution you want (and even that is tricky). Otherwise all those fixed to float to fixed to float going on in your code are going to add all sorts of numerical problems - each loses information.
In case you did not miss it: I’ve worked with and supported financial systems which do exactly that for a very long time without any micro-numerical issues^. Otoh, floating-point is a constant source of microbugs, unless all your developers are Knuth-level pros who are also versed in consulting and have no monday mornings or deadlines. It doesn’t matter if an error is O(N < 1e6) or O(1) when an underwater comparison to a limit fails and control flow triggers randomly.
^ The last [few] cent problem is usually handled explicitly, either naively (last=rest) or in Bresenham-like way (rarely, when it matters) and is easily catched in an accounting balance when left unhandled
I've been down this argument with other HN commenters some time back and explicitly demonstrated that it fails when you do it this way. It's not worth chasing down all the details again.
The short answer is it will fail, and in unexpected places. The only correct answer when doing this to is do the numerical analysis completely and correctly. This half-assed "it rounds the error away" is completely insufficient (and wrong).
The problem with letting such error slop around in code is that someone will take your code and use it to aggregate 1m loans, then your 25 bits of safety just became real money. Then someone will leverage that routine and add more problems.
When you build the lowest pieces so sloppily, it quickly contaminates the whole system. Make each piece as numerically solid as possible, otherwise you will get bitten.
If you have not proven your algorithm correct using numerical analysis for this stuff, it is not correct. End of story.
>but repeating multiplication doesn’t appear in finance naturally
Yes it does - compound interest if you need periods and tables.
And we're in agreement - floating point, not fixed point, is how to do financial calculations. I'm amazed how many people on HN want to argue that fixed point works when it's easy to demonstrate it fails in terrible ways and is significantly more error prone than simply using doubles (or double- or quad- doubles when needed).
Yes it does - compound interest if you need periods and tables.
Only if you don’t round to fixed before capitalizing. But when you don’t, numerically less savvy investors (99.9% of people) would just ask to fix it and stop being so smart. They want deterministic output for any particular end of period.
I see you’re coming from academic side, but real world doesn’t work like that. Nobody’s going to take our algorithms and shove 2^(>20) records of sums greater than $100M into them.
Worth noting that this is a risk whenever there is rounding, if this chain of logic happens with cents as ints, it is still wrong:
1 / 2 => 0
0 * 2 => 0
Therefore: 1 == 0 (a + b) >= c
suddenly fails to work. For instance, in Excel the expression A1-B1-C1 >= 0
returns FALSE when A1=2.03, B1=0.03, and C1=2. Such an expression can be used for instance to filter records that fall within a certain range, and that filter would produce wrong results.All the reporting software I have seen in banks use decimals for adding numbers.
If you are adding small numbers together, those errors are negligible and get rounded out in the result (you usually can't pay an amount with more than two decimals). It's only a problem if somehow you are doing some calculations that need to be exact on amount large enough that the float rounding starts affecting your pennies.
Financial reporting has materiality thresholds, no one cares about pennies if the size of a balance sheet is trillons, the materiality will likely be in millions, not the least because the numbers will be shown in millions in the financial report and the numbers won't be additive because of rounding) and for a BI tool a number with 12 digits is unreadable and too much information to be useful.
If you are doing pricing, also no one really cares about pennies on a 1 million payment.
> PS. If you design software that works with money amounts, always use fixed point decimals. Don't use floats, it's just wrong!
Well, it depends. If all you do is add and substract numbers, ok, and that's what they typically do. If you need to do any other calculation (and most financial software does), this will bite you as percentages and ratios will be rounded aggressively, and multiplying large amounts will overflow.
Not very suitable for complex scientific calculations of course, but perfect for web development which is more likely to deal with money than scientific calculations.
[1] https://docs.python.org/3/library/decimal.html#context-objec...
[1] https://www.stackage.org/haddock/lts-20.10/base-4.16.4.0/Pre...
[1] https://python-history.blogspot.com/2009/03/problem-with-int...
The next level solution is to apply generators so that either the decimal stream or the continued fraction is allowed to be infinitely precise, but I think this can have dangerous effects where checking whether a number is equal to 0 or maybe 1 can involve infinite computation? So that's where you really understand “oh, I do really need that epsilon, for comparisons’ sake.”
For continued fractions I think you can also just have your library bound the size of the integers involved? So “it’s an array of signed int32s, but if your continued fraction generates a number that would overflow that, we just truncate the stream at that point.” Then the library is able to say that these two things are equal because their difference is [0; int_overflow] which becomes just [0]. Something like that.
which is pretty darn amazing. pi and e are essentially first class, but a lot of transcendentals aren't. seems like a really neat approach.
That's actually a really interesting question - while this is obviously true for most functions which (in a mathematical sense) exist, I wonder if it's true for "all functions weighted by their use in computing applications"? That is - do boring old "addition, subtraction, and multiplication of integers" outweigh division, trigonometrics, etc.?
In 3D modelling/video games, almost certainly not. In accounting software...probably? Across the whole universe of programs: who could say?
[1] https://peps.python.org/pep-0465/#so-is-good-for-matrix-form...
Since addition, subtraction, multiplication, and modulus are each used more than division and exponentiation _combined_ (and since not every use of those last two functions would result in an "inexact" result), I think we can pretty clearly conclude that "most usages of mathematical operators in these libraries will result in an 'exact' result" (I'm hand-waving on the definition of "exact", I don't think it's at issue here)
Which is not, of course, a good justification for ceasing to worry about the problem, since a) those packages might not be representative of all libraries, and b) a small proportion of uses might result in a disproportionate amount of bugs.
This is what concerns me. Sure, using decimal floating point solves 0.1 + 0.2 = 0.3 (which I can't imagine ever writing in real code). But if you get used to that, then you start to expect 0.1*x + 0.2*x to be 0.3*x, and depending what x is this may or may not be true. Maybe it works for all of your test cases (because your test cases are things like 2 and 10^-4), but then you accept some user input and start getting weird bugs (or infinite loops). There is no good solution besides expecting and preparing for rounding error.
This reminded me of an article I read awhile ago, probably here on HN:
https://randomascii.wordpress.com/2014/01/27/theres-only-fou...
Though regardless of usage, I think that people doing stuff that needs floats are more likely to understand why they need them, and have the ability to use them explicitly without much issue. By using python, and most other high level languages, we're already making sacrifices to make things easier to use and understand, and in Python specifically we're told that explicit is better than implicit, except for this.
Sure, but how common is that? Exponentiation is almost always positive when I've seen it.
>In 3D modelling/video games, almost certainly not.
A 32-bit integer divided into a 16-bit whole and a 16-bit fraction would be limited to only representing values between -32768 and 32767 while also having worse precision than a 32-bit ieee std754 floating-point at values near 0.
>In accounting software...probably?
Representing money in terms of cents instead of dollars removes the need for real-numbers entirely outside of "Office Space" scenarios where tracking fractions of cents over millions of transactions adds up to tangible amounts of money.
>Across the whole universe of programs: who could say?
Most computer programs don't need real numbers of any sort, and the ones that do need to be written by people who understand basic mathematical concepts like precision.
...OK? I'm not sure how that relates to my supposition that trigonometric operations are likely to be more common in 3D modelling cases. I'm not arguing for or against any particular representation of numbers therein.
> Representing money in terms of cents instead of dollars removes the need for real-numbers entirely
I wasn't imagining dollars-and-cents, but rather rates - X per Y, the most natural way in which division arises in real life.
> Most computer programs don't need real numbers of any sort...
You're again arguing against a case I'm not making. I'm not making any claims about the necessity (or otherwise) of real numbers in programs, but simply wondering about the prevalence of particular operations.
> and the ones that do need to be written by people who understand basic mathematical concepts like precision.
A snide insult motivated by your own misunderstanding of my point. I understand precision, and it's irrelevant to my point.
jeez talk about arguing against cases i didnt make, i never said you dont understand precision.
And will break when the version reaches 1.10. While I agree we need a better way to teach this (e.g. inexact-exact distinction as in Scheme or more recently Pyret), that's as problematic as storing a telephone number as an integer (or worse, a FP number).
f = float(closest_to=0.1)
You can't really mess up programmer expectations like this. let foo = 0.1 + 0.2;
the vast majority of the time people want 0.1 and 0.2 to be decimals, not floats, so they should default to that.But more to the point of your question, we use floating point math because that's what computers are good at, not because we want that for it's own sake. We want to figure out sales tax, or how long until our kid will need to buy new shoes, or what effect changing the speed limit had on the total number of accidents, or all kinds of other things that humans care about. Using a floating point representation may be the most efficient way to get some of those answers using the technology available, but it is just a step along the way, not what we actually want. That's what I mean by implementation detail.
Basically, I think that any literals typed into the interpreter should work the same way as a calculator. If you want something special for your implementation because it will work better or faster, then that should be explicit.
How your calculator handles rounding is itself an implementation detail. I have no idea what rules your calculator uses to round and it’s not in any standard. Does my calculator do the same thing? Unknowable.
It’s the conversation from an abstract model to a concrete instantiation where floats are used, generally out of necessity or ignorance.
The fewer details needed to do this conversion, the easier it is to develop programs. When I say easier, I mean it’s faster AND less buggy — since the conversation often involves introduces errors, subtleties, and logic not present in the abstract model.
Would it? I thought dealing with integers - a value, in binary - would be faster than floats - a value in binary, a decimal places value, whatever odd logic there is required to hide the leaky abstraction.
Edit: nevermind. Since the conversation was 'decimal versus float' I thought 'decimal' meant integers without floating points.
If decimals means a decimal point, I think a better suggestion would be to use integers.
Ironically, this is a much better description of `decimal` than of `float`.
IEEE float math is done in hardware. It is "one value in binary" that is added, subtracted, multiplied, etc etc with electric circuits.
The decimal abstraction requires manually keeping track of the number of significant digits, converting back and forth so that two different decimals can be added / multiplied, etc etc. There's a lot more that has to happen besides asking the CPU to do a single operation.
Its easy to think of "decimal" as one thing because every language provides a library called `decimal`, but there are a million subtle decisions and tradeoffs to make when choosing one standard binary representation. Most languages don't have a binary representation at all, and implement `decimal` as a high level abstraction with regular integers.
On modern CPUs, it's faster to cast a number to double, do a square root and cast back to int, than even the cleverest bithacking int algorithm.
I would think that, nowadays, every child would learn that calculators do not always produce exact answers almost in kindergarten.
Also (nitpick), it’s not “float vs decimal”. “Floating vs fixed point” and “binary vs decimal” are orthogonal issues.
such a strange comment to make. the number of people that would ever bump into this situation is so small. like the difference of .1 + .2 = .3 and .30000000000000004
i just used my iPhone to do (√2)² and it displayed 2 as the result. same for 3 x 1/3 to receive an answer of 1. i can only assume that the default android calculator app would behave the same. between those 2 apps, we've probably covered the default calculator for the majority of people.
gotta break out of the HN is the world shell, and realize the majority of people do not suffer the same issues you might deal with on a daily basis.
Why not rational numbers?
Money transfers tend to involve payments with fixed precisions. For cash, it's often rounded to the smallest available coin, for credit cards, you can add a few more digits, but it will still be fixed point.
When you settle a transaction, you typically need to pay exactly the amount defined by the transaction because some system is doing some check that the amount is exactly right.
So in my example above, if your friends are paying $33.33 you may have to pay $33.34 to make the transaction go through.
When you're writing systems involved in actual money transfers (or accounting) you tend to be better off using fixed point decimal, with predefined precision for each variable involved.
Within a calculation, it may be fine to use float, but the number going into ledgers or transactions need to have the predefined precision.
Fixed point (ie calculate everything in cents) is a good convention for lots of things to do with money. But it's not a good convention to have as the default in a language like Python.
> Within a calculation, it may be fine to use float, but the number going into ledgers or transactions need to have the predefined precision.
I was not suggesting the use of float. I was suggesting using rational numbers by default. See eg https://docs.python.org/3/library/fractions.html
To many, a rounding error makes the answer "wrong", and suddenly the tool has switched from a reliable one into an untrustworthy one.
By middle school, kids should have learned that you can't trust calculators. There are all sorts of numbers like pi, e, sqrt(2) that are impossible to represent. Once you start getting into trig, you have to accept rounding.
Explaining _why_ .1 isn't representable requires explaining IEEE-754 and explaining _that_ requires an understanding of binary numeric representation.
I teach college students who find this confusing, so I think it's fair that the average person finds floating point behavior confusing (in fact, I've had to explain to Physics Professors doing computation simulation work why their 1-<tiny number> isn't working out the way they expect -- though they initially tried using double doubles to get around the problem).
i=0
while(i<1):
<something with i>
i=i+.1
And spending hours trying to figure out why it ran an extra iteration, and this was early enough it wasn't easily googleable. Whatever I was doing with i needed it to be .1, .2, .3... and thought I was being clever not doing 1...10 and dividing by 10 every iteration within the loop. I think there was also a weird language quirk with whatever I was using that a print(i) rounded to a handful of decimal places so it looked fine while debugging it.Very frustrating, but in retrospect very eye opening.
I think that's a good lesson for kids.
That's how I learned about it years ago.
(My Haskell-fu isn't deep, but I suspect it would even be possible to write it so that, for example, multiplication of two division operation expressions multiplied the numerators together instead of doing divide -> divide -> multiply...).
$ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help
Prelude> :m +Data.Ratio
Prelude Data.Ratio> :t (%)
(%) :: Integral a => a -> a -> Ratio a
Prelude Data.Ratio> 18 % 21
6 % 7
Prelude Data.Ratio> 1%10 + 2%10
3 % 10
There's even Data.CReal for working with the computable reals: Prelude> :m +Data.CReal Data.Complex
Prelude Data.CReal Data.Complex> let i = 0 :+ 1
Prelude Data.CReal Data.Complex> exp (i * pi) + 1 :: Complex (CReal 0)
0 :+ 0Then you need to do some analysis on your AST to try and restructure it in a way that preserves as much precision as possible.
You don’t really need Haskell for this though, in theory it will work in any language (but more ergonomically if you have operator overloading).
For example, I'm a huge fan of TypeScript, but it is hamstrung by the fact that javascript only supports a single `number` type (and, recently, `bigint`). Worse is the effect that since JSON is derived from javascript, it also has no built-in decimal type. So what happens inevitably when you want to represent stuff like money:
1. First people start using plain numbers, then they eventually hit the issues like this post.
2. Then they have to decide how they will represent decimals in things like APIs. Decimal string? Integers that represent pennies or some fraction of pennies?
3. Also, pretty much all databases support decimals natively, so then you get into this weird mash of how to not lose precision when transferring data to and from the DB.
Overall it's just definitely one of those issues that programmers hit and rediscover again and again and again. I'm surprised there hasn't been more movement towards a better language-level solution for the post popular language in use worldwide.
> The JSON syntax does not impose any restrictions on the strings used as names, does not require that name strings be unique, and does not assign any significance to the ordering of name/value pairs. These are all semantic considerations that may be defined by JSON processors or in specifications defining specific uses of JSON for data interchange.
So, in reality, the JSON "spec" is really how the most popular implementations interpret it. I'm not aware of a single implementation (though I could most definitely be wrong) that interprets number tokens as anything but floats/doubles by default.
https://learn.microsoft.com/en-us/dotnet/api/system.decimal?...
Really wish JS had added first class support for a bigdecimal class before bigint. After all, the first is basically a superset of the latter.
IEEE 64 bit floats are accurate to just past 15 decimal digits. For ordinary monetary amounts, the exact figure in cents is approximated with ridiculous precision. If you rub two pennies together, you are likely causing more of a difference in the amount of copper than the IEEE 64 bit float causes in the value.
You have to do many, many additions and multiplications before you get a result which has accumulated so much error that it is now closer to the wrong penny. E.g. if you don't deal with dollar amounts more than 7 figures, you have about 8 places past the decimal point; you need something like a 6 place error before the penny is affected.
You can counteract this problem by correcting intermediate results to that floating-point value which is closest to exact penny result. In other words, throughout your calculation, you truncate away the difference between the result, and the best approximation of the dollar and cent value.
Within this framework, you can implement all required rounding rules, too. You can take a floating-point result representing a fraction of a penny and round it according to banker's rule to the penny.
Of course, you can't just ignore the issue and just blindly use floating-point for money in a serious accounting system; but that's a strawman version of using floating-point for money.
Also, Microsoft Excel uses floating point. See here:
https://learn.microsoft.com/en-us/office/troubleshoot/excel/...
Vast armies of people rely on Excel for financial calculations.
> You can counteract this problem by correcting intermediate results to that floating-point value which is closest to exact penny result
The argument seems to be "floats are okay, as long as you're careful", but forgetting to round the number in between a large number of operations is a probable mistake.
Using decimals would make such a mistake impossible.
Decimals are software libs; what could go wrong?
More people use the floating-point instructions of a popular CPU than any given decimal library.
If you're starting from scratch, it's probably a lot less work to write (test and debug) a Money class based on floating-point, whose overloaded math operations do the required rounding (so that code using the class cannot forget) than to make a Money class based on decimal arithmetic.
(The last time I wrote an accounting system, I made a Money class based on integers. It could be retargeted to other representations easily. I could make the change and compare the ledger totals and other reports to see if there is a difference.)
Perhaps, but how many of the former are in a position to notice accuracy issues? I have more faith in a reputable decimal library than your average FPU, frankly.
Bugs in libraries do exist, but it's much easier to fix a bug in one place, than to track down every single line where floating point operations could misbehave.
> IEEE 64 bit floats
That should be IEEE 64-bit floating-point. The name `float` is a 32-bit floating-point.
> are accurate to just past 15 decimal digits.
FOR SOME RANGE. They're called floating-point because they can modify how much scaling is devoted to either side of the decimal point. The more scale it has to devote to representing whole portion, the lower its precision in the fractional portion. At some point, a 64-bit floating-point value cannot represent any decimal digits. Past around 2^53, a `double` 64-bit floating-point value cannot even represent every whole number, because at that point the available precision is 2 or more.
If I say "64 bit float", it's obviously not that one.
Here is CLISP:
[1]> (type-of 3.0)
SINGLE-FLOAT
[2]> (type-of 3.0d0)
DOUBLE-FLOAT
Python3: >>> type(3.0)
<class 'float'>
>>> type(3.0e300)
<class 'float'>
Looks like it's calling the 64 bit ones just float.Rust has f32 and f64. Some historic languages have used type names like REAL and DOUBLE and others.
"IEEE 64 bit float" is almost unambiguous; it could be the binary one or the decimal one.
IEEE uses identifiers like binary64, decimal32, if you want to be pedantic, and not float and double.
> FOR SOME RANGE ...
Your comment is seems to be based on the wrong idea of what the number of digits means. An IEEE 64 bit binary float stores 15 significant digits without loss. In that C language that gives us the float type, the preprocessor constant DBL_DIG has a value of 15 (on IEEE floating point platforms).
This is a 15 digit number using E notation (in terms of digits of precision / significant figures):
1.23456789012345E-20
So is this; it is not a 16 digit number: 123456789012345.0
And so is this isn't a 17 digit one: 12345678901234500.0 (same as 1.23456789012345E16)
You have to write the number in exponentatial notation, and chop the trailing zeros. Then count the remaining number number of digits in the mantissa part.I think it's only for subnormal numbers that the 15 rule breaks down; but I might have mentioned that. These are special representations close to zero beyond what is reachable with the regular exponent and mantissa, which have the benefit of certain desirable behaviors in underflow situations.
if (walletBalance - sumOfWithdrawals < 0) { throw new Error('overdrawn); }
Try that code in Javascript where walletBalance = 0.3 and the sumOfWithdrawals = 0.2 + 0.1.Point being there are tons of operations in the financial world where you check things against 0, or want to ensure that a breakdown of smaller transactions equals a larger amount. Those all can fail with floating points but succeed with decimals.
I think this is a pretty user-friendly compromise.
By default, when it not longer fits, it will convert to floating point (called Num in Raku). But this behaviour can be set: another alternative is for the numerator and denominator both being big integers. This gives you infinite precision rational numbers, at the expense of potentially needing infinite CPU and/or RAM.
But like the parent poster noted, hardware tends to not actually implement the decimal float parts (I mean, IEEE 754 doesn't care about how calculations are made or how fast they are, so a software emulation is perfectly acceptable from the standard perspective). I think IBM POWER has one of the rare HW implementations of decimal floats.
:)
(with a multiplication to enter and a division to exit)
Acceleration for binary fixed precision was (still is I guess) common in DSPs. Not decimal fixed though.
I lost track of which extensions Intel provides, but I wouldn't be surprised if something was available.
https://docs.oracle.com/cd/E19120-01/open.solaris/817-5477/e...
So I caution against blind preference for rational representations as well. You really have to choose your numeric representation based on your use case. It's unfortunate that this can be so hard to control precisely in many programming languages.
It also appears that the logic for implemented something like this standard is indeed slower than standard IEEE754. Even if it’s only a bit slower, seems bad to make it the default.
All this just to fix something which is confusing to a novice programmer… and this is leaving aside the additional complications a fixed width decimal has which a floating point type doesn’t have.
What would be much slower is all the native code that Python apps use for bulk math, such as numpy. And that is because we don't have decimal floating point implemented in hardware, not because of some fundamental limitation. If decimals were more popular in programming, I'm sure the CPUs would quickly start providing optimized instructions for them, much like BCD was handled when it was popular.
That leaves vanilla Python. You’re right that compared to dynamic dispatch, a decimal op implemented in hardware is nothing, but the issue here (IMO) is death by a thousand cuts. Python is already slow as hell, and currently using decimal as a default would require a software implementation in many if not all places, which will be slow. Trade off does not seem worth it to me.
b) Floats are efficient approximations of real numbers. Trigonometry and logarithms are vastly more important than having the numbers be printed pretty, so defaulting to rationals instead of reals is quite insane.
Citation needed. I suspect more people are doing accountancy than physics simulations.
But what if... what if they had a bastard child? What if we moved the point a fixed distance in decimal... and also a floating distance in binary?
The value represented would then be sign * mantissa * 2^exponent * 10^bias
With a bias of -6, you could represent every multiple of 0.000001 up to 9 billion if I did the math correctly.
When Python originally made the choice to have literals with decimal points in them be floats, the language did not have a decimal implementation, so floats were the only choice.
I don't know if anyone has proposed changing the default now that Python does have a decimal implementation, but I suspect that such a proposal would be rejected by the Python developers as breaking too much existing code.
What would be almost as nice, and would be backwards compatible, would be introducing a more compact way to declare decimal literals, something like "0.1d".
You realize that decimal numbers also have the exact same types of rounding issues as binary numbers right? The only difference is that the former allows you to divide by both 2 and 5 cleanly, whereas the latter only lets you divide by powers of 2. If you want to divide by 3, 7, 11, or compute a square root or an exponent, using decimals is not going to save you from having to reason about rounding.
> To me, 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125 feels less surprising than 0.1 + 0.2 = 0.30000000000000004.
The other thing that I would mention is that I see some really gnarly workarounds to try to get around this... Just bump up to integers for a second! People have this mistaken idea that the best way to understand these rounding “errors” is that floating point is just unpredictably noisy for everything, and that's not true.
Floating point has an exact representation of all integers up to 2^53 – 1. If you are dealing with dollars and cents that clients are getting billed or whatever, okay, the best thing to do is to have a decimal library. But if you don't have a decimal library and it's just some in-game currency that you don't want to get these gnarly decimals on, 3/10 will always give 0.3. 4/100 will always give 0.04. Just use the fact that the integer arithmetic is exact: multiply by the base, round to nearest integer, do your math, and then divide out the base in the end: and you'll be good.
C# has decimal in the base library. We are doing a new project with financial data, and decided we willvhave everything in decimal - no floats at all.
There is no point of dealing with these issues to save irrelevant amount of CPU
Sometimes you really do need to have a pretty good estimate of pi dollars, but often not.
If the wikipedia article on Decimalisation[1] is complete and accurate, only Mauritania and Madagascar still have non-decimal currencies.
If you really needed it to be uniform, you could work in 1/1000th worldwide, as long as you didn't need to keep more decimals for other reasons.
And compiler can't help you on the application UI layer.
Fractions in positional notations are not exact as a rule. There are some exceptions, but mostly they are not exact. 1/3, 1/6, 1/7, 1/9 cannot be represented by decimals exactly (or they can, but using infinite amount of digits in their representation). There are exceptions of course, for example for decimals you need denominator with no prime factors except 2 and 5. For binary it can be only 2.
> some things can be represented in finite digits in base x but require infinite digits in base y.
Very good summary. Binary to decimal is very straightforward until fractions require infinite digits. I don’t think dec64[1] is even a tradeoff—it’s just better. The significand stays a normal binary number— but it encodes the decimal point in gasp decimal. No infinities required for the numeric language that we all think in.
[1] https://en.wikipedia.org/wiki/Decimal64_floating-point_forma...
Not to be confused with Douglas Crockford's DEC64 [1], which I believe is worse than binary floating points.
This assertion does not withstand scrutiny. You may like being able to get True as the result of 0.1 + 0.2 == 0.3, but the landscape will still be littered with rounding errors as soon as you try to do anything nontrivial. (Or even plenty of trivial things like expecting 1/6 + 1/6 to add to 1/3). So all you gain is a false sense of security in exchange for less precision and slower computation.
(Of course, there are plenty of tasks for which floats are just wrong for the job, and you should transform the problem so that you can use integers or rationals instead. For example, when you are incrementing a number by (integer multiples of a) fixed delta, just change units so you can count numbers of increments as an integer, and change units back at the end.)
I started using decimal in all of the new features in an effort to mitigate this but it resulted in even more headache as I now had a bunch of casts floating around on top of the truncation band-aids that I had to implement for existing lengthier calculations. My plan was to refactor the whole app and SQL schema but if memory serves I got pulled onto something more pressing before I had the chance.
This was especially disappointing to me because this was all implemented in C# and T-SQL which are languages with first-class support for decimal numbers. It wouldn't surprise me if the app is still in use today with some hapless dev halfway across the country whacking these bugs as they pop up.
[1] See QuickJS's BigDecimal extension for example: https://bellard.org/quickjs/jsbignum.html#Properties-of-the-...
Or with C# 128 bit implementation of decimal?
Is there no good implementation anywhere?
What Every Computer Scientist Should Know About Floating-Point Arithmetic
One thing worth mentioning is that the IEEE 754 floating point standard is implemented in hardware in most CPUs (microcontrollers might deviate) so if you learn this stuff you’ll be set for life, as in, it doesn’t depend on the programming language you’re using.
There's also a stackoverflow thread: https://stackoverflow.com/q/588004
The problem isn't binary itself, even. It's that our tools pretend that it's decimal, misleading people. If it looks like decimal, it should behave as one by default.
There was a post on /r/softwaregore recently where someone showed a progress bar on Steam that said "57/100 achievements! Game 56% complete" or something like that. I snarkily commented something about naively using floor() on floating point and then moved on.
But then I thought that that may not have been the problem, fired up emacs and wrote a C program basically just saying
printf("%f\n", 57.0 / 100.0 * 100.0);
To my surprise this correctly gave 57.000000, but in python, 57 / 100 * 100
Gave 56.999... anybody know what was up here? Different algorithms for printing fp? printf("%f\n", 57.0 / 100.0 * 100.0 - 57.0);
It just happens that `%f` in C defaults to 6 fractional digits.The expression is likely subject to an optimization known as constant folding: the compiler calculates the value of the constant expression, and substitutes that value into the code.
However, that constant-folding calculation has to produce the same result as what would happen at run-time: the 56.999999999999992895... approximation of 57.
Constant-folding having to produce the same results as run-time creates a challenge in cross-compiling situations, when the host machine's math is different from the target machine's math. The compiler must emulate the target machine math.
gcc fp.c -o fpIf 56.999999999999 is rounded to 6 digits after the decimal point, you get 57.000000.
:)
Or, how about this: #include <stdio.h>
int main(void)
{
for (int prec = 0; prec < 25; prec++)
{
printf("%.*f\n", prec, 57.0 / 100.0 * 100.0);
}
return 0;
}
Output: 57
57.0
57.00
57.000
57.0000
57.00000
57.000000
57.0000000
57.00000000
57.000000000
57.0000000000
57.00000000000
57.000000000000
57.0000000000000
56.99999999999999
56.999999999999993
56.9999999999999929
56.99999999999999289
56.999999999999992895
56.9999999999999928946
56.99999999999999289457
56.999999999999992894573
56.9999999999999928945726
56.99999999999999289457264
56.999999999999992894572642
The 64 bit double will store 15 decimal digits reliably. That is to say, if you have a decimal figure with 15 significant digits, which is in range of the type (and not mapping to a denormal value close to zero and whatnot), all 15 digits are representable and can be recovered.In the reverse direction, you need about 17 decimal digits in order to capture an 64 bit double as decimal text such that the exact value can be recovered from the decimal text.
Thus in the above loop's output, once we are past 17 digits (including the 56 before the decimal point), we are no longer seeing any new data, just a continuation of the fraction.
And, notice how the last value that is still 57.000.... is exactly 15 digits wide. The next row is 16 digits, and that's where we now have 56.9999.... but 16 digits isn't quite enough to capture the value. I believe the next row gets us that: the ...99929. If we use that as a constant, any digits after that make no difference.
Programming languages which, by default, print floating-point values to 15 digits will show the nice result .1 + .2 = .3.
This is what I did in TXR Lisp.
1> *print-flo-precision*
15
2> (+ .1 .2)
0.3
3> (set *print-flo-precision* 16)
16
4> (+ .1 .2)
0.3
5> (set *print-flo-precision* 17)
17
6> (+ .1 .2)
0.30000000000000004
We can see there is no value difference in digits beyond 17: 7> (eq 0.30000000000000004 0.300000000000000049)
t
8> (eq 0.30000000000000004 0.300000000000000040)
t
To get different value (different floating-point bit pattern), we need a difference in the 17th digit. And not just a single increment: 9> (eq 0.30000000000000004 0.30000000000000003)
t
10> (eq 0.30000000000000004 0.30000000000000002)
t
The last digit being 3 and 2 is still mapping to the same value. When we make it 1, we start getting a different float: 11> (eq 0.30000000000000004 0.30000000000000001)
nilPeople won't like this, so perhaps Steam put in some logic to adjust the numbers, and it is also affecting your case.
EDIT: But I would have gone for ceiling(99*achievements/total), which does give 57% in this case.
Anyway, thought I'd give you some background. This stuff is easy to look up too. You're probably getting downvoted because HN doesn't really like unsubstantive comments.
Not unjustifiably; without specifically referring to any posts you may have made, such comments in general add no insight or value to the discussion. It's just noise that has to be scrolled past.
(The same goes for discussing getting downvoted, which is discouraged by the HN guidelines.)
The problem is when converting decimals. Now instead of each digit being tenths/hundredths/thousandths, we have halfs/fourths/eights/etc. Now try out the problem yourself. Imagine you had a formula in the form of
(1/2)x + (1/4)y + (1/8)z + (1/16)a + (1/32)b ...
Try to find a solution that adds up to exactly 0.1 and you'll see that it can't be done. Computers just get as close as they can.
It's a display thing, not an algorithm thing. It rounds by default at a certain length, previously 17 digits.
php > echo PHP_VERSION;
8.2.1
php > $zeropointthree = 0.1 + 0.2;
php > echo $zeropointthree;
0.3
php > ini_set('precision', 100);
php > echo $zeropointthree;
0.3000000000000000444089209850062616169452667236328125
https://www.php.net/manual/en/ini.core.php#ini.precisionWe've all been told about float imprecision in a very hand-wavy way that doesn't actually explain anything at all about the problem. On its face, one would assume that any two binary values being added would have a reliable and deterministic value, but that's not how floats work. There's some magic in the stack that causes errors to creep in, which isn't something we see with other types of numerical representation. It's very easy to show and understand that 01b + 01b = 10b, but that logic somehow doesn't apply to floats.
I think it's a very interesting subject and I wish there was more discussion of the real causes rather than just "oh yeah, floats do that sometimes, just ignore it"
It's a solution that never fails (by hypothesis, you aren't working with sin or pi or gamma or some shit), and for every practical input the denominator stays small enough that it fits nicely into machine registers. You have the bigint fallback for the edge cases, in case they're needed.
Floats IMO are only suitable when you're actually okay with approximate results (perhaps including known convergence properties so that you can reify an ostensibly floating point result into more exact values).
https://spectrum.ieee.org/floating-point-numbers-posits-proc...
https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/posits...
Unfortunately, they are not a solution to the OPs problem, which is fundamentally embedded in the architecture of computers. One has to find an appropriate representation for the needed numbers in bits and bytes.
In Python one can use fractions or decimals, if the float format is not good enough. Other options are fixed point arithmetic or arbitrary precision arithmetic. Choose one that combines the needed characteristics with the least amount of work.
0.1 in binary, is a repeating decimal - just like how 1/3 in base 10 is a repeating decimal.
So you get errors due to rounding.
This is also why it's super important in loops to never use "=" as a condition statement, but instead use "<=" (or ">="). Otherwise you might create an infinite loop.
Or something like this?
But if you are storing the weight of a product a float might be fine - you don't really care if the system thinks you have 50.000001 pounds of product when you have 50 pounds (because your scale isn't that accurate anyway).
Floats will generally give you better performance than decimal types as well.
decimal n = 123.456m;Also, a plain integer is usually sufficient for financial calculations (just count pennies, not dollars) so in some ways I think decimal types are overused when an integer would do just fine.
Never seems a bit strong, it depends on the context. You can use = on int types all day.
I'm not sure what you mean; those aren't integers (if that's what you mean by "whole number float"):
python3
>>> 0.4 == 0.6 - 0.2
False
(edit: formatting for python)
I'm assuming that you are interviewing candidates where this bug is not common.
Who cares if the candidate comes from a field where this isn't common? That's the point. You want to hire people who would know what to do if they came across it.
It's amazing the amount of hostility here towards a perfectly sensible interview question and process.
Do you work in a field involving physical measurements like OP does?
Thanks, Julia!
You never get this kind of result with fixed point or binary coded decimal math.
The computer Language M never has this kind of problem.
The base of your number system does not need to be the same as the base associated with the fixed point position.
Easiest to explain: if you have a uint64 that represents a monetary value, you can let it express the number of dollarcents instead of dollars. Then you can express 1/10 dollar as 10 dollarcents.
Welcome to Racket v8.7 [cs].
> (/ 1 3)
1/3
> (+ (/ 1 3) (/ 1 3) (/ 1 3))
1
> (integer? (+ (/ 1 3) (/ 1 3) (/ 1 3)))
#tThat is, if the exponent is a power of 2 you can write 1/2, 1/4, 1/8 exactly but you can't write 1/3, 1/5, 1/10, etc.
If the exponent is base 10 then you can write 1/5, 1/10, 1/1000 and such exactly.
Note the mantissa and the exponent are both integers and so far as this problem is concerned it does not matter if these are written in binary or BCD or some other representation. There is some controversy about what is better, if you use a binary mantissa the math is a little faster and more accurate, if you use a decimal mantissa conversions to and from ASCII are quicker and ASCII conversions are a major part of real life math workloads.
I've long thought that this problem is one of a list of problems that many people encounter on the path to bending computers to their will and that some people decide that computer programming isn't for them because of this kind of problem. I think the kind of person who learns Python to put their outside-of-computing skills on wheels is particularly affected.
It is a "disruptive technology" problem because the person who is using IEEE floats heavily has accommodated to this problem and would not give up the slightest amount of performance. Decimal FP can be implemented in software but is slow in software. IBM has had hardware Decimal FP in their mainframes for a very long time and there is even an IEEE standard. (A company that has been using mainframes for a long time cut a check for the wrong amount because the abused the number system back in 1963 and thus learned their lesson a long time ago.)
The best hope I have is that the "social justice" people can be led to believe that unintuitive numerics keep underrepresented people out of the field and that they threaten Intel that they'll tear down their headquarters unless they catch up to where mainframes were 50 years ago. It could be a huge win for the industry and for the DEI office because employers would have to buy everyone a new computer and even white guys might think the DEI office was doing good work if it meant they got to replace their 5 year old corporate craptop. I mean, how is it that a few people with two fingers get to oppress all the rest of us with ten?
This specific result is a result of binary floating point math with a particular precision. More precision, or decimal floating point, will fix that, but have similar kinds of errors for the same operation on different numbers. fixed-size BCD/fixed-point math has other limitations, its not a general solution.
The general solution is to have a numeric tower where representations and operations meet the following rules:
1. The default representation of any exact literal (without a modifier representing a particular inexact representation, e.g., as an optimization or a necessity of interfacing with an external library) is exact,
2. Any operation on any operations between exact representations that can be done exactly is unless specified otherwise (as it might be for the same reasons discussed above), and stored in a representation that can represent the result exactly.
3. Essentially inexact operations or operations on inexact numbers are conducted in a way and produce output representations that minimize additional imprecision introduced, except when explicitly specified otherwise.
Computer algebra systems where the “top level representation is symbolic are potentially the ultimate expression of this, but the Scheme numeric tower is pretty good (but at least Racket, and I think schemes in general, represent exact decimal fractions as floats still, so don’t entirely avoid the problem, but at least division of integers produces exact rationals.) Lots of languages default to putting numbers expressed as literals into either fixed-sized integers (not bad, especially when those are often 64-bit now which rarely has much practical distinction from arbitrary precision in most applications) or fixed-sized binary floats (which are more problematic, especially given the mismatch between clean binary and clean decimal representations.) This is very good for efficiency, because computers can process fixed sized integers and binary floats very quickly. But, especially for floats, it can be bad for correctness when doing arithmetic where the input is all clean decimal literals.
[0] https://standards.scheme.org/corrected-r7rs/r7rs-Z-H-8.html#...
> The computer Language M
Which one? MUMPS (also known as M), or the Power Query Formula Language (also known as M)? OR something else?
M guarantees over 15 digits of precision, which is why it is heavily used in banking and financial applications
$ racket
Welcome to Racket
> (read-decimal-as-inexact #f)
> (+ 0.1 0.2)
3/10
Lovely.From the tutorial:
Herbie rewrites floating point expressions to make them more accurate. Floating point arithmetic is inaccurate; even 0.1 + 0.2 ≠ 0.3 for a computer. Herbie helps find and fix these mysterious inaccuracies.
> there’s a floating point number that’s closer to 0.3 than 0.30000000000000004!
But then you read to the end and find out this wasn’t true, and the answer to the clickbait headline is just, “Because it’s the closest floating-point value to the correct answer”.
It's not very complicated when you see it in bits
And C/C++ doesn't have binary coded decimal natively. If it did, then much of what we use decimals and fractions for would be easier to show in decimal.
Real numbers are simulated using a finite set of bits.
So equality comparisons are not useful for floating point numbers
This is computing 101
Because we have 10 fingers rather than 8.
procedure main() if (0.1 + 0.2 == 0.3) then { write("true") } end
It prints "true".
Storing the number of dollars in you savings account - us decimal or better yet integer (just count the pennies).
CS101 -- can we please move on.