From: Nicholas Clark Date: Wed, 22 Aug 2001 20:59:05 +0000 (+0100) Subject: 64 bit integer preserving pp_divide X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=5479d1922c9c0436fce71aca57c0f7e68ece0b49;p=p5sagit%2Fp5-mst-13.2.git 64 bit integer preserving pp_divide Message-Id: <20010822205905.U82818@plum.flirble.org> p4raw-id: //depot/perl@11725 --- diff --git a/pp.c b/pp.c index e470d1c..1855b2d 100644 --- a/pp.c +++ b/pp.c @@ -999,29 +999,111 @@ PP(pp_multiply) PP(pp_divide) { dSP; dATARGET; tryAMAGICbin(div,opASSIGN); - { - dPOPPOPnnrl; - NV value; - if (right == 0.0) - DIE(aTHX_ "Illegal division by zero"); + /* Only try to do UV divide first + if ((SLOPPYDIVIDE is true) or + (PERL_PRESERVE_IVUV is true and one or both SV is a UV too large + to preserve)) + The assumption is that it is better to use floating point divide + whenever possible, only doing integer divide first if we can't be sure. + If NV_PRESERVES_UV is true then we know at compile time that no UV + can be too large to preserve, so don't need to compile the code to + test the size of UVs. */ + #ifdef SLOPPYDIVIDE - /* insure that 20./5. == 4. */ - { - IV k; - if ((NV)I_V(left) == left && - (NV)I_V(right) == right && - (k = I_V(left)/I_V(right))*I_V(right) == I_V(left)) { - value = k; - } - else { - value = left / right; - } - } +# define PERL_TRY_UV_DIVIDE + /* ensure that 20./5. == 4. */ #else - value = left / right; +# ifdef PERL_PRESERVE_IVUV +# ifndef NV_PRESERVES_UV +# define PERL_TRY_UV_DIVIDE +# endif +# endif #endif - PUSHn( value ); - RETURN; + +#ifdef PERL_TRY_UV_DIVIDE + SvIV_please(TOPs); + if (SvIOK(TOPs)) { + SvIV_please(TOPm1s); + if (SvIOK(TOPm1s)) { + bool left_non_neg = SvUOK(TOPm1s); + bool right_non_neg = SvUOK(TOPs); + UV left; + UV right; + + if (right_non_neg) { + right = SvUVX(TOPs); + } + else { + IV biv = SvIVX(TOPs); + if (biv >= 0) { + right = biv; + right_non_neg = TRUE; /* effectively it's a UV now */ + } + else { + right = -biv; + } + } + /* historically undef()/0 gives a "Use of uninitialized value" + warning before dieing, hence this test goes here. + If it were immediately before the second SvIV_please, then + DIE() would be invoked before left was even inspected, so + no inpsection would give no warning. */ + if (right == 0) + DIE(aTHX_ "Illegal division by zero"); + + if (left_non_neg) { + left = SvUVX(TOPm1s); + } + else { + IV aiv = SvIVX(TOPm1s); + if (aiv >= 0) { + left = aiv; + left_non_neg = TRUE; /* effectively it's a UV now */ + } + else { + left = -aiv; + } + } + + if (left >= right +#ifdef SLOPPYDIVIDE + /* For sloppy divide we always attempt integer division. */ +#else + /* Otherwise we only attempt it if either or both operands + would not be preserved by an NV. If both fit in NVs + we fall through to the NV divide code below. */ + && ((left > ((UV)1 << NV_PRESERVES_UV_BITS)) + || (right > ((UV)1 << NV_PRESERVES_UV_BITS))) +#endif + ) { + /* Integer division can't overflow, but it can be imprecise. */ + UV result = left / right; + if (result * right == left) { + SP--; /* result is valid */ + if (left_non_neg == right_non_neg) { + /* signs identical, result is positive. */ + SETu( result ); + RETURN; + } + /* 2s complement assumption */ + if (result <= (UV)IV_MIN) + SETi( -result ); + else { + /* It's exact but too negative for IV. */ + SETn( -(NV)result ); + } + RETURN; + } /* tried integer divide but it was not an integer result */ + } /* else (abs(result) < 1.0) or (both UVs in range for NV) */ + } /* left wasn't SvIOK */ + } /* right wasn't SvIOK */ +#endif /* PERL_TRY_UV_DIVIDE */ + { + dPOPPOPnnrl; + if (right == 0.0) + DIE(aTHX_ "Illegal division by zero"); + PUSHn( left / right ); + RETURN; } } diff --git a/t/op/64bitint.t b/t/op/64bitint.t index c34d188..e5ff95b 100644 --- a/t/op/64bitint.t +++ b/t/op/64bitint.t @@ -16,7 +16,7 @@ BEGIN { # 32+ bit integers don't cause noise no warnings qw(overflow portable); -print "1..58\n"; +print "1..59\n"; my $q = 12345678901; my $r = 23456789012; @@ -325,5 +325,13 @@ $q += 0; print "# \"18446744073709551616e0\" += 0 gives $q\nnot " if "$q" eq "18446744073709551615"; print "ok 58\n"; +# 0xFFFFFFFFFFFFFFFF == 1 * 3 * 5 * 17 * 257 * 641 * 65537 * 6700417' +$q = 0xFFFFFFFFFFFFFFFF / 3; +if ($q == 0x5555555555555555 and $q != 0x5555555555555556) { + print "ok 59\n"; +} else { + print "not ok 59 # 0xFFFFFFFFFFFFFFFF / 3 = $q\n"; + print "# Should not be floating point\n" if $q =~ tr/e.//; +} # eof diff --git a/t/op/arith.t b/t/op/arith.t index 8b8e2bc..890c78f 100755 --- a/t/op/arith.t +++ b/t/op/arith.t @@ -1,6 +1,6 @@ #!./perl -w -print "1..113\n"; +print "1..130\n"; sub try ($$) { print +($_[1] ? "ok" : "not ok"), " $_[0]\n"; @@ -211,3 +211,29 @@ tryeq 110, 1 + " 1", 2; tryeq 111, 3 + " -1", 2; tryeq 112, 1.2, " 1.2"; tryeq 113, -1.2, " -1.2"; + +# divide + +tryeq 114, 28/14, 2; +tryeq 115, 28/-7, -4; +tryeq 116, -28/4, -7; +tryeq 117, -28/-2, 14; + +tryeq 118, 0x80000000/1, 0x80000000; +tryeq 119, 0x80000000/-1, -0x80000000; +tryeq 120, -0x80000000/1, -0x80000000; +tryeq 121, -0x80000000/-1, 0x80000000; + +# The example for sloppy divide, rigged to avoid the peephole optimiser. +tryeq 122, "20." / "5.", 4; + +tryeq 123, 2.5 / 2, 1.25; +tryeq 124, 3.5 / -2, -1.75; +tryeq 125, -4.5 / 2, -2.25; +tryeq 126, -5.5 / -2, 2.75; + +# Bluuurg if your floating point can't accurately cope with powers of 2 +tryeq 127, 18446744073709551616/1, 18446744073709551616; +tryeq 128, 18446744073709551616/2, 9223372036854775808; +tryeq 129, 18446744073709551616/4294967296, 4294967296; +tryeq 130, 18446744073709551616/9223372036854775808, 2;