What I didn’t know about Ruby Numbers
dumas-olivier.medium.com
dumas-olivier.medium.com
- they are binary rather than decimal, so rounding behavior doesn’t match what humans expect
- some floating point operations have rounding/error accumulation
You can multiply and divide a float by 2 all day long and every result will be exact, with no error accumulation. However, adding floats together is a different story :-)
That said, decimal types are still much preferable for anything that needs to be “exact” for human usage. It’s very easy to make a mistake with floats, even if they could support your use case.
I think you're meaning 'exact' as in they're deterministic. I don't know anyone who knows what they're talking about that argues against this, so I think you're not disagreeing with anyone.
Other people mean 'not exact' as in they can't represent all real numbers. Which is true.
If, however, you meant "the integer datatypes can't represent all integers", then you probably want to say that instead of mixing maths terminology. And you'll also have to back up that claim because bigintegers are as accurate as you have space to store them in; their limitation is not imposed by their spec, but by your hardware.
Unlike IEEE floating point decimal numbers, where the limitation is explicitly part of the spec itself. That limitation is literally why floats even work at all.
Perhaps it would be clearer to say that floats and floating point operations are deterministic, so you don't have to convince someone that 0.30000000000000004 is the exact answer to 0.1 + 0.2
There's no need to use a float operation to see this inexact behaviour - floats cannot even represent most reals that humans can write in a computer program. If you write `0.1` in your programming language of choice such that it gets treated as a float, the result is not, in any way, exactly 0.1.
It might be fair to claim that floats are more inexact - certainly moreso than Rational, but probably moreso than BigDecimal for the numbers that programmers tend to care about.
I expect that many use cases for floats do not involve any literals at all, of course.
It's not clear what you're getting at here. Are you just saying that each floating-point value precisely represents a real number? Or are you saying that floating-point arithmetic is often exact? [0]
My own pedantic nitpick: the article states that but BigDecimals are [exact]. BigDecimal is only exact in the way floats are exact. BigDecimal has arbitrary precision, but is not able to precisely represent every real. There's no way to come up with a system that would be capable of doing this. No matter how much memory you throw at BigDecimal, you can't precisely represent pi.
[0] Sterbenz Lemma, on which there are surprisingly few good sources. See page 45 of (PDF) http://lyoncalcul.univ-lyon1.fr/ed/DOCS_2012-2013/FloatingPo...
Anyway, it's an interesting topic imo :)
0.1 + 0.2 == 0.3
[1] formerly known as Perl6.Anywhere you used Floats, you can just replace them with BigDecimals. They work just the same. Some people get scared by them because they are always printed in scientific notation in the console, but you can just call `.to_s` on them to see them in a regular format:
123.45.to_d => 0.12345e3
123.45.to_s => "123.45"
So don't get worried about the display. The behave exactly the same as a float, you can sum, multiply, and so on, and then you call .to_s when it's time to display it.
Also any BigDecimal#round will give you an integer (just like .to_i will), but you can also call Bigdecimal#round(2) to round to, for instance, 2 decimal places.
Also, if you are coding anything related to currencies, I strongly suggest you always use integers to store the amounts in cents. And when you need to divide it for calculations, just call `.to_d`, do your math, and then `.round` at the end, so you discard the fractions of cents. It works wonders, and the math will add up just like in Excel :)
BigDecimal("123.45") == 123.45.to_d
=> true
Counterexample:
BigDecimal("1.5999999999999996") == 1.5999999999999996.to_d
=> false
The most ergonomic way to initialize decimal numbers is IMO "123.45".to_d
(Note the quotes around the number. `.to_d` is being called on a string.)BigDecimal's only lossless API is through strings. It would be nice to have native support for decimals at the parser level:
0d123.45 => BigDecimal("123.45")One of the test vectors was 5.80, but when we were printing them out to 15 digits of precision (the maximum a double is guaranteed to preserve) we were seeing "5.80000019073486", where of course we expected to see "5.8".
As it turns out this is the closest to 5.8 you can get in single precision so the remote system was obviously sourcing these from a C 'float'.
sigh
The thing is, it's actually 'fine' until you actually need to do a calculation. Once you get there you need to convert to decimal and round to however many dp's you should be using.