in reply to Re^4: Largest integer in 64-bit perl
in thread Largest integer in 64-bit perl

Perl does this by switching to double floats ...

Rather, perl switches to "NV" floats.
Of course, I'm nitpicking, but it's a nitpick that does have some significance.
On a perl whose ivsize=8, && whose nvtype is either 'long double' or '__float 128', I get:
D:\>perl -e "print 'wtf' unless ~0 < (~0) + 1;" D:\>
But on a perl where ivsize=8 and nvtype is 'double' and you get:
D:\>perl -e "print 'wtf' unless ~0 < (~0) + 1;" wtf D:\>
Now that particular configuration (ivsize==8 and nvtype is 'double') is the only perl configuration that produces that nonsense - and yet it's the most commonly used configuration !!
If you use either an ivsize of 4, or an nvtype that is not double, then you can ignore that particular weirdness because it's not going to happen.

Note: That "nonsense" doesn't just happen with (~0) + 1. It happens for (~0) + $x for all $x in the range 1..2048.
That's mainly why I think of it as an insane configuration. (It's still a configuration that I regularly use ;-)

Cheers,
Rob

Replies are listed 'Best First'.
Re^6: Largest integer in 64-bit perl
by LanX (Saint) on May 30, 2025 at 01:21 UTC
    So what do you expect to happen if you construct the max unsigned integer via bitwise inverting zero ~0 and adding 1 ?

    You are talking about "weirdness", so what would be "normal" in your opinion.

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

      You are talking about "weirdness", so what would be "normal" in your opinion.

      I just find it weird that, on a platform where 64-bit positive integer values can be stored exactly, it is considered acceptable that all other integer values be rounded to 53 bits of precision.
      I would find it quite acceptable if positive integer values greater than 64 bits were rounded to 64-bit (or greater) precision values, but cutting them back to 53-bit precision values seems a very poor alternative.
      My gripe is not so much about how perl handles this configuration, but that this daft configuration is so widely used and accepted as valid. (I guess I should be grateful that the chosen default NV was the double precision one, and not the single precision float ;-)

      Of course, people are free to accept whatever they like, and thankfully it's easy enough to find modules (or to use a sanely configured perl build) such that precision is not reduced to below-integer precision when the integer value overflows integer precision.

      Cheers,
      Rob
        > I just find it weird that, on a platform where 64-bit positive integer values can be stored exactly, it is considered acceptable that all other integer values be rounded to 53 bits of precision.

        I need to test again, but IMHO this depends on the number before the operation being a float or not.

        AFAIK is exponentiation ** using an approximation algorithm and hence always producing a float.

        So even if 2**53 (-1) should be a perfectly "whole number" it's stored as a float.

        But that's speculation, I'm thinking of writing simple test code using only basic operations.

        This could be translated to different dynamically typed languages to test their behavior.

        update

        I can't see how your wtf-example shows any rounding to 2**53.

        Your floats have 64 bits leaving 53 for the mantissa and ~0 == 2^64-1 , hence there is no way to represent a whole number ~0+1 loss free.

        Going up to 128bit floats will just replicate the situation on 32bit systems with 64bit floats.

        update

        OK I tried to nail down the weirdness, please note in the first example I'm constructing 2^53-1 and everything goes fine, even since ** will always produce a NV, summing them up will convert back to IV.

        lanx:$ perl -MDevel::Peek -E'$x+=2**$_ for 0..52;Dump ($x);Dump($x+1)' SV = IV(0x64c9edf22bb0) at 0x64c9edf22bc0 REFCNT = 1 FLAGS = (IOK,pIOK) IV = 9007199254740991 SV = IV(0x64c9edf838d0) at 0x64c9edf838e0 REFCNT = 1 FLAGS = (PADTMP,IOK,pIOK) IV = 9007199254740992

        Now the same with 2^54-1, because the last element of the sum is the NV 2**53 which doesn't fit into the mantissa, we are stuck in NV even after adding an IV.

        lanx:$ perl -MDevel::Peek -E'$x+=2**$_ for 0..53;Dump ($x);Dump($x+1); +say "*** WTF *** " if $x == $x+1' SV = PVNV(0x5ea09827d340) at 0x5ea0982abd00 REFCNT = 1 FLAGS = (NOK,pNOK) IV = 9007199254740991 NV = 18014398509481984 PV = 0 SV = NV(0x5ea09830cb18) at 0x5ea09830cb30 REFCNT = 1 FLAGS = (PADTMP,NOK,pNOK) NV = 18014398509481984 *** WTF ***

        The real issue here is the ** operand, it should ideally produce an integer since 2 and 53 are integers and they fit into IV.

        But even special casing the X**Y algorithm for integer results isn't trivial.

        • 2**65 would still need to produce an NV
        • 4**0.5 would need to be IV 2

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

      The most sensible approach is to break out bigint/bigrat when it is needed. It's a scripting language, afterall, where we didn't get to declare the types. Python does it, and what would have been Perl 6 did it. I think it would be quite sensible for someone to come up with a new Perl 5 feature flag called use experimental "rational_math"; and bundle a copy of GMP with perl (or Tom-math or something) and have perl seamlessly support rational numbers. It would be much more "normal" than asking for "please give me a semi-arbitrary circumstantial mixture of double and int64_t".

      Of course, that's a huge amount of work to specify and implement and write tests for, so I don't expect it to happen. But it would have saved me an awful lot of grief with rounding problems related to money over the years.

        It would be much more "normal"

        The great thing about "normal" is that it means completely different things to different people. In this case, DWIM is "give me something close enough to what I want" and if you're after something exact in number theory terms you probably/should know the incantation for subverting/upgrading this. IMVHO.


        🦛

        > The most sensible approach is to break out bigint/bigrat when it is needed. ... Python does it

        Really? That's surprising me a bit... can you provide evidence for this?

        From my experience is Python "more" typed than Perl, leading to 3/2==1 because it assumes that's an integer operation only.

        edit
        OK, looks good in Python3, seems they also fixed the division issue from Python2
        :~$ python3 Python 3.12.3 (main, Feb 4 2025, 14:48:35) [GCC 13.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 3/2 1.5 >>> (2**4711+1)-2**4711 1 >>>

        update

        LOL, I suppose nobody is perfect...

        >>> (2**(128/2)+1)-2**64 0.0 >>> (2**(64)+1)-2**64 1 >>> (2**(128//2)+1)-2**64 1 >>>

        Seems like 128/2 being a float, breaks the workaround xD

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