in reply to Re: Detecting whether UV fits into an NV in thread Detecting whether UV fits into an NV
Why not just cast the UV value to an NV, cast back to an UV, and see if the first and final values differ?
I think that checking whether the UV arg == (UV)((NV)arg) is a splendid idea.
Interestingly, it's only very slightly faster (at least for the value ranges I've tested) than the XSub I posted at the beginning of this thread, but it's far, far simpler.
As an aside, whilst this approach is quite simple to implement inside XS space, is it even possible to do inside perl space ?
That is, inside a perl sub, how would one coerce a UV to an NV and then back to a UV ?
(This aspect is not an issue for me - which is the reason that I've presented it "as an aside". Just curious as to if/how it's possible, that's all.)
Thanks Dave.
Cheers, Rob
Re^3: Detecting whether UV fits into an NV
by dave_the_m (Monsignor) on Feb 26, 2020 at 12:48 UTC
|
In general perl goes to great trouble to use the type which will be as lossless as possible rather than the type you want, so it wouldn't be easy. Maybe messing about with pack/unpack?
Dave. | [reply] |
Re^3: Detecting whether UV fits into an NV
by syphilis (Archbishop) on Feb 27, 2020 at 05:57 UTC
|
I think that checking whether the UV arg == (UV)((NV)arg) is a splendid idea
Unfortunately this check fails for some UV values with less recent Microsoft Compilers - eg Visual Studio 2010 and earlier.
No problems with Visual Studio 2017 and Visual Studio 2019.
I suspect that Visual Studio 2015 is the oldest Microsoft Compiler that doesn't suffer from this problem, but I haven't confirmed that.
This particular issue looks like another manifestation of the problem that sent me down this path in the first place.
Here's the demo I ran:
use strict;
use warnings;
use Inline C => Config =>
BUILD_NOISY => 1;
use Inline C => <<'EOC';
int uv_fits_double1( UV arg ) {
if(arg == (UV)((NV)arg)) return 1;
return 0;
}
int uv_fits_double2(UV arg) {
while(!(arg & 1)) arg >>= 1;
if(arg < 9007199254740993) return 1;
return 0;
}
EOC
my $ls = 63;
my @in = ( 18446744073709549568, 18446744073709139968);
for(@in) { print "$_: ", uv_fits_double1($_),uv_fits_double2($_), "\n"
+; }
__END__
With Windows Server 2003 SP1 Platform SDK, this outputs:
18446744073709549568: 01
18446744073709139968: 01
Base 2 representations of the 2 Uvs is (resp):
1111111111111111111111111111111111111111111111111111100000000000
1111111111111111111111111111111111111111111110011011100000000000
showing that both are exactly representable by a double.
Cheers, Rob | [reply] [d/l] |
|
Once you phrased it this way, I realized you finally gave me an in-the-wild use-case for my Data::IEEE754::Tools. Thanks! :-)
#!/usr/bin/env perl
use strict;
use warnings;
use Data::IEEE754::Tools qw/ulp/;
sub uv_fits_double_using_ulp {
0 == ($_[0] % ($_[0]/ulp($_[0])))
}
my @in = map { no warnings 'portable'; chomp; oct('0b'.$_) } <DATA>;
for(@in) {
my $d = $_/ulp($_);
printf "%-24s: %4d %32.15f %16d %1s\n", $_, ulp($_), $_/ulp($_), $
+_ % $d, uv_fits_double_using_ulp($_) ? 'T' : 'F';
}
__END__
1111111111111111111111111111111111111111111111111111100000000000
1111111111111111111111111111111111111111111111111111110000000000
1111111111111111111111111111111111111111111111111111100000000001
1111111111111111111111111111111111111111111110011011100000000000
1111111111111111111111111111111111111111111111111111100000000000
0111111111111111111111111111111111111111111111111111110000000000
0011111111111111111111111111111111111111111111111111111000000000
0001111111111111111111111111111111111111111111111111111100000000
0000111111111111111111111111111111111111111111111111111110000000
0000011111111111111111111111111111111111111111111111111111000000
0000001111111111111111111111111111111111111111111111111111100000
0000000111111111111111111111111111111111111111111111111111110000
0000000011111111111111111111111111111111111111111111111111111000
0000000001111111111111111111111111111111111111111111111111111100
0000000000111111111111111111111111111111111111111111111111111110
0000000000011111111111111111111111111111111111111111111111111111
0000000000111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
which gives
18446744073709549568 : 2048 9007199254740991.000000000000000
+ 0 T
18446744073709550592 : 4096 4503599627370496.000000000000000 450359
+9627369472 F
18446744073709549569 : 2048 9007199254740991.000000000000000
+ 1 F
18446744073709139968 : 2048 9007199254740791.000000000000000
+ 0 T
18446744073709549568 : 2048 9007199254740991.000000000000000
+ 0 T
9223372036854774784 : 1024 9007199254740991.000000000000000
+ 0 T
4611686018427387392 : 512 9007199254740991.000000000000000
+ 0 T
2305843009213693696 : 256 9007199254740991.000000000000000
+ 0 T
1152921504606846848 : 128 9007199254740991.000000000000000
+ 0 T
576460752303423424 : 64 9007199254740991.000000000000000
+ 0 T
288230376151711712 : 32 9007199254740991.000000000000000
+ 0 T
144115188075855856 : 16 9007199254740991.000000000000000
+ 0 T
72057594037927928 : 8 9007199254740991.000000000000000
+ 0 T
36028797018963964 : 4 9007199254740991.000000000000000
+ 0 T
18014398509481982 : 2 9007199254740991.000000000000000
+ 0 T
9007199254740991 : 1 9007199254740991.000000000000000
+ 0 T
18014398509481983 : 4 4503599627370496.000000000000000 450359
+9627370495 F
18446744073709551615 : 4096 4503599627370496.000000000000000 450359
+9627370495 F
I doubt it's faster than some of hte others | [reply] [d/l] [select] |
Re^3: Detecting whether UV fits into an NV
by ikegami (Patriarch) on Feb 27, 2020 at 07:29 UTC
|
my $x = ( 1 << 54 ) | 1;
say $x; # 18014398509481985
$x = unpack 'F', pack 'F', $x;
$x = unpack 'J', pack 'J', $x;
say $x; # 18014398509481984
To support negative numbers, use I instead of J for negative numbers.
The following is a little bit truer to a cast, but it's far more complicated, far more fragile (can fail for overloaded and magical vars), and technically uses XS:
use strict;
use warnings;
use feature qw( say );
use B qw( svref_2object );
my $x = ( 1 << 54 ) | 1;
say $x; # 18014398509481985
# Add the value as an NV to the scalar.
{ no warnings qw( void ); log $x; }
# Make it so the scalar contains just an NV.
$x = svref_2object(\$x)->NV;
# Add the value as an IV or UV to the scalar.
{ no warnings qw( void ); $x | 1; }
# Make it so the scalar contains just an IV or UV.
$x = svref_2object(\$x)->int_value;
say $x; # 18014398509481984
| [reply] [d/l] [select] |
|
Cool - and thanks for going to the trouble of providing that. (I can spend hours trying to get pack and unpack to do what I want them to do ;-)
Both of your approaches still fail to deal correctly with certain values when older Microsoft compilers are used.
All is good for the value of 18014398509481985, irrespective of compiler. The value of $x changes from 18014398509481985 to 18014398509481984, indicating correctly that 18014398509481985 is not representable as a double.
However, IIUC, the value of $x should not alter if $x is initialized to (eg) 18446744073709549568 or 18446744073709139968 as both values are representable as a double.
But those values do change with those older Microsoft compilers - again, I believe, indicative of that same problem (that I keep hitting) that afflicted Microsoft compilers until some point after Visual Studio 2010 and no later than Visual Studio 2017.
At this point, the only thing that's working on these troublesome Microsoft compilers are the subs I provided in my initial post.
I should point out that I haven't yet got to assessing jcb's contribution. I'll try to get to it over the weekend, or whenever I can find the time.
Cheers, Rob
| [reply] |
|
|