Hacker News new | ask | show | jobs
by nulld3v 383 days ago
I'm curious where you got this idea from because it is trivially disprovable by typing 0.1 or 0.01 into any python or JS REPL?
2 comments

Do you believe that the way the REPL prints a number is the way it's stored internally? If so, explaining this will be a fun exercise:

    $ python3
    Python 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> a = 0.1
    >>> a + a + a
    0.30000000000000004
By way of explanation, the algorithm used to render a floating point number to text used in most languages these days is to find the shortest string representation that will parse back to an identical bit pattern. This has the direct effect of causing a REPL to print what you typed in. (Well, within certain ranges of "reasonable" inputs.) But this doesn't mean that the language stores what you typed in - just an approximation of it.
Oddly, tcl prints 0.30000000000000004 while jimtcl prints 0.3, while with 1/7 both crap out and round it to a simple 0.

Edit: Now it does it fine after inputting floats:

puts [ expr { 1.0/7.0 } ]

Eforth on top of Subleq, a very small and dumb virtual machine:

     1 f 7 f f/ f.
     0.143 ok

Still, using rationals where possible (and mod operations otherwise) gives a great 'precision', except for irrationals.
:facepalm: my bad, I completely missed the more rational intepretation of OP's comment...

I interpreted "directly representable" as "uniquely representable", all < 15 digit decimals are uniquely represented in fp64 so it is always safe to roundtrip between those decimals <-> f64, though indeed this guarantee is lost once you perform any math.

https://docs.python.org/3/tutorial/floatingpoint.html

Stop at any finite number of bits, and you get an approximation. On most machines today, floats are approximated using a binary fraction with the numerator using the first 53 bits starting with the most significant bit and with the denominator as a power of two. In the case of 1/10, the binary fraction is 3602879701896397 / 2 * 55 which is close to but not exactly equal to the true value of 1/10.

Many users are not aware of the approximation because of the way values are displayed. Python only prints a decimal approximation to the true decimal value of the binary approximation stored by the machine. On most machines, if Python were to print the true decimal value of the binary approximation stored for 0.1, it would have to display:

  0.1
  0.1000000000000000055511151231257827021181583404541015625
That is more digits than most people find useful, so Python keeps the number of digits manageable by displaying a rounded value instead:

  1 / 10
  0.1
That being said, double should be fine unless you're aggregating trillions of low cost transactions. (API calls?)
For anyone curious about testing it themselves and/or wanting to try other numbers:

  >>> from decimal import Decimal
  >>> Decimal(0.1)
  Decimal('0.1000000000000000055511151231257827021181583404541015625')