Perl / perl5

🐪 The Perl programming language
https://dev.perl.org/perl5/
Other
1.85k stars 524 forks source link

% operator could not produce (IV_MIN - 1) #22122

Closed t-a-k closed 2 weeks ago

t-a-k commented 3 weeks ago

I found a corner case where % (modulo) operator returns wrong result when the result should be (IV_MIN - 1):

For 32-bit (i686-linux) perl:

$ perl -wle 'my $divisor = -0xc0000000; print "$_ % $divisor = ", $_ % $divisor for 0x3FFFFFFE .. 0x40000000'
1073741822 % -3221225472 = -2147483650
1073741823 % -3221225472 = 2147483647
1073741824 % -3221225472 = -2147483648

For -Duse64bitint perl:

$ $ perl -le 'my $divisor = -0xc000000000000000; print "$_ % $divisor = ", $_ % $divisor for 0x3FFFFFFFFFFFFFFE .. 0x4000000000000000'
4611686018427387902 % -1.38350580552822e+19 = -9.22337203685478e+18
4611686018427387903 % -1.38350580552822e+19 = 9223372036854775807
4611686018427387904 % -1.38350580552822e+19 = -9223372036854775808

I think the second result of both example above should be contiguous to the first and third results (or at least should be negative, as perlop says that $m % $n will be less than or equal to zero if $n is negative).

I hope this pull request will fix this.

sisyphus commented 3 weeks ago

LGTM.

For 64 bit builds, the problem you've found applies to all of my 32-bit and 64-bit builds of perl-5.39.9 on Windows, irrespective of nvtype and ivsize, in that the (appropriate) one-liner you've provided always returns a positive value for the middle calculation.

AIUI, X % -Y should be zero if X % Y is zero. Otherwise it should be ( X % Y) - Y

With your patches in place, your one liner outputs as follows. For 64-bit perl whose nvtype is double:

4611686018427387902 % -1.38350580552822e+19 = -9.22337203685478e+18
4611686018427387903 % -1.38350580552822e+19 = -9.22337203685478e+18
4611686018427387904 % -1.38350580552822e+19 = -9223372036854775808

The first 2 results are, in fact, identical. But given that both -9223372036854775809 and -9223372036854775810 cannot be accurately represented on this configuration of perl (either as IV or NV), I think that's fair enough.

For 64-bit perl whose nvtype is 80-bit extended precision long double:

4611686018427387902 % -1.38350580552821637e+19 = -9.22337203685477581e+18
4611686018427387903 % -1.38350580552821637e+19 = -9.22337203685477581e+18
4611686018427387904 % -1.38350580552821637e+19 = -9223372036854775808

Despite appearances, these are the expected values of -9223372036854775810, -9223372036854775809 and -9223372036854775808. We have sane results, but an unfortunate presentation that has rounded the first 2 of them to 18 significant digits .

For 64-bit perl whose nvtype is __float128:

4611686018427387902 % -13835058055282163712 = -9223372036854775810
4611686018427387903 % -13835058055282163712 = -9223372036854775809
4611686018427387904 % -13835058055282163712 = -9223372036854775808

At last !! ... sane results && presented sanely. I expected the long double build to present identical output, and I don't know why it didn't. (Maybe that requires that the integer be expressible in no more than 18 digits - but it's a separate issue, anyway) Note also the sane presentation of the value -0xc000000000000000. (The long double build could be made capable of doing the same.)

For 32-bit builds with IVSIZE of 4, the 32-bit one liner is also fixed by the patches, producing:

1073741822 % -3221225472 = -2147483650
1073741823 % -3221225472 = -2147483649
1073741824 % -3221225472 = -2147483648

irrespective of nvtype.

After patching the 32-bit builds that have IVSIZE of 8, I find that the 64-bit one liner matches the outputs of the 64-bit builds for each of the 3 nvtypes. (It would have been surprising if that wasn't the case.)

All good, AFAICS.