http://qs321.pair.com?node_id=11123122


in reply to Re: int() function
in thread int() function

> It does that for all floating point values - always to 15 decimal digits (unless perl's nvtype is other than "double") and always for a dubious reason.

The mantissa of a double has 52 bits°, you need 50 bits to encode 15 decimal digits ( 2**10 =1024, so rule of thumb 10 bits for 3 decimals)

So the last two bits are used for error correction, since they don't suffice to encode another decimal digit.

Is it a good solution? Well I'd say appropriate for the era and not "for a dubious reason".

Are there better approaches? Sure, but they are harder to implement and would have been quite slow back in the days.

From my experience, >95% of the problems arise from calculating with currencies and the solution is obvious, calculate with integer cents and move the point only for output.

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

°) WP actually says 53 by using a spare bit redundancy , but tl;dr and this still fails to encode 16 decimals.

Replies are listed 'Best First'.
Re^3: int() function
by syphilis (Archbishop) on Oct 25, 2020 at 00:46 UTC
    Well I'd say appropriate for the era and not "for a dubious reason".

    I probably should have said "for lack of a sound reason". (Hmmm ... not sure if that's any different ;-)

    It's just that, if the stringification provided an extra 2 decimal digits of precision, we would avoid having to look at rubbish diagnoses like this one (where the test fails but "got" and "expected" are reported as being the same):
    C:\_32>perl -MTest::More -le "cmp_ok(0.14, '==', 1.4 / 10, '0.14 == 1. +4/10'); done _testing();" not ok 1 - 0.14 == 1.4/10 # Failed test '0.14 == 0.14' # at -e line 1. # got: 0.14 # expected: 0.14 1..1 # Looks like you failed 1 test of 1.
    IMO, if $x is an NV, then the condition "$x" == $x should always be true unless $x is NaN.
    And that's the way it would be if doubles stringified to 17 digits of precision instead of the current 15 digits.
    I would regard that as being a significant improvement for very little cost.
    And we would then see that "got" is 0.14000000000000001 and "expected" is 0.13999999999999999 - which at least makes some sense.

    It's still not ideal because the strings "0.14000000000000001" and "0.14" both assign to the same double - so why print out all of those extra digits ?
    Python3 (and Raku, I believe) use as few digits as are needed and would report the double 0.14 as being "0.14" and not "0.14000000000000001".

    I've implemented that Python3/Raku behaviour in Math::MPFR - though with a different algorithm and probably not as efficiently as Python3/Raku.
    C:\>perl -MMath::MPFR="nvtoa" -le "print nvtoa(0.14);print nvtoa(1.4 / + 10);" 0.14 0.13999999999999999
    Cheers,
    Rob
      > if the stringification provided an extra 2 decimal digits

      How this? There are only 2-3 bits left and 2**3 < 10, so no way to get a 16th decimal out of a double.

      I know it's confusing. But if you stuff a number with more than 15 decimals into a double you'll have loss anyway. And I doubt it's better in JS or python.

      > "cmp_ok(0.14, '==', 1.4 / 10

      Well, but on the other hand eq should work because of the magic.

      IMHO most people understand rounding errors, the point of confusion is that exact decimal fractions are not always exact binarie floats.

      Probably we should have a shortcut for something like printf "%.18f" to facilitate diagnosis.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        I know it's confusing

        I don't find it so.

        But if you stuff a number with more than 15 decimals into a double you'll have loss anyway.

        One thing I can guarantee is that if you limit yourself to 15 decimal digits, you will experience the loss of not being able to assign the full range of "double" values.
        As an example:
        C:\perl -le "printf '%a', 1.41421356237312;" 0x1.6a09e667f3c3dp+0 C:\>perl -le "printf '%a', 1.41421356237313;" 0x1.6a09e667f3c6ap+0
        Between 0x1.6a...c3d and 0x1.6a...c6a there exists 45 doubles with distinct, precise values. (c6a - c3d == 2d == 45 in base 10)
        But you can't assign to any of those 45 values if you limit yourself to assigning 15-digit values. (For some of those values 16 digits will suffice, but you'll never need more than 17 digits.)
        Also, for each of those 45 values, perl will output that they are either 1.41421356237312 or 1.41421356237313.
        That's 45 precise, distinct, unique values - for which perl will display only 2 values.
        Perl is making itself look ridiculous for the sake of saving 2 digits of precision.

        And I doubt it's better in JS or python

        Python2 was crap, but python3 (as I've already mentioned in this thread) is exemplary:
        $ python3 -c "print(0.14)" 0.14 $ python3 -c "print(1.4 / 10)" 0.13999999999999999 $ python3 -c "print(2 ** 0.5)" 1.4142135623730951
        Python3 will always output the minimum number of decimal digits needed to preserve the uniqueness of the given value. And, last time I checked, raku was outputting exactly the same as python3 - for "doubles", anyway.

        Not sure what happens with javascript - I've no experience with it.

        Cheers,
        Rob
Re^3: int() function
by ikegami (Patriarch) on Oct 30, 2020 at 12:11 UTC

    It has 53 bits of precision because there's an implied leading 1 bit that's not stored.

    log10(2^53) ~ 15.95, not quite 16. So not exactly 16 decimal digits of precision, but damn close. What's important here is that it's more than 15.

    $ perl -e' printf "%.16f\n", 0.1234567890123456; printf "%.16f\n", 0.1234567890123457; ' 0.1234567890123456 0.1234567890123457