Skip to content

Commit

Permalink
Use IEEE-style Rationals for ±Inf/NaN Num conversions
Browse files Browse the repository at this point in the history
Fixes RT#130459: https://rt.perl.org/Ticket/Display.html?id=130459
Fixes RT#130171: https://rt.perl.org/Ticket/Display.html?id=130171
Fixes RT#128857: https://rt.perl.org/Ticket/Display.html?id=128857

Fix follows TimToady++'s recommendation:
https://irclog.perlgeek.de/perl6-dev/2016-08-06#i_12976358

The best I could trace the history of changes that led to introduction
of special Num-numerator Rationals is (in my eyes) a misunderstanding
of TimToady's evals[^1] where along with conversion[^2] of ±Inf/NaN to
Rational, they were made[^3] non-explosively stringifiable. That, in
turn, made all division by zero non-explosive in Str view, so
Num-numerator Rationals were introduced to fix that issue.

The fix in this commit simply goes back to using ±1/0 numerators for
±Inf/NaN Rationals and have Str view explode for them (just as it
does already for any other zero-denominator Rational).

The decision to use ±1 numerator for Infs is deliberate as is NOT
having .new() normalize numerators for zero-denominator Rationals to ±1,
since that seems to me to just throw away information for no good reason.
The result of that decision is that, for example, <42/0>.perl and
<42/0>.Num.Rat.perl will differ, with the latter becoming <1/0>, but I
think that's fine and both are still `==` to each other.

[1] https://irclog.perlgeek.de/perl6-dev/2016-05-21#i_12521558
[2] 1f3ca6469759c79aa809d60a77
[3] 7a4ca4d90769ea03913f6a5eb8
[4] 498d0a4ae4572a84f80c0c5726
  • Loading branch information
zoffixznet committed Dec 30, 2017
1 parent 4945dc4 commit 042cb74
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 48 deletions.
17 changes: 8 additions & 9 deletions src/core/Num.pm
@@ -1,7 +1,6 @@
my class X::Cannot::Capture { ... }
my class X::Numeric::DivideByZero { ... }
my class X::Numeric::CannotConvert { ... }
my role Rational { ... }

my class Num does Real { # declared in BOOTSTRAP
# class Num is Cool
Expand Down Expand Up @@ -44,18 +43,20 @@ my class Num does Real { # declared in BOOTSTRAP
}

method Rat(Num:D: Real $epsilon = 1.0e-6, :$fat) {
return Rational[Num,Int].new(self,0)
if nqp::isnanorinf(nqp::unbox_n(self));
my \RAT = $fat ?? FatRat !! Rat;

return RAT.new: (
nqp::iseq_n(self, self) ?? nqp::iseq_n(self, Inf) ?? 1 !! -1 !! 0
), 0
if nqp::isnanorinf(nqp::unbox_n(self));

my Num $num = self;
$num = -$num if (my int $signum = $num < 0);
my num $r = $num - floor($num);

# basically have an Int
if nqp::iseq_n($r,0e0) {
$fat
?? FatRat.new(nqp::fromnum_I(self,Int),1)
!! Rat.new(nqp::fromnum_I(self,Int),1)
RAT.new(nqp::fromnum_I(self,Int),1)
}

# find convergents of the continued fraction.
Expand Down Expand Up @@ -84,9 +85,7 @@ my class Num does Real { # declared in BOOTSTRAP
# smaller denominator but it is not (necessarily) the Rational
# with the smallest denominator that has less than $epsilon error.
# However, to find that Rational would take more processing.
$fat
?? FatRat.new($signum ?? -$b !! $b, $d)
!! Rat.new($signum ?? -$b !! $b, $d)
RAT.new($signum ?? -$b !! $b, $d)
}
}
method FatRat(Num:D: Real $epsilon = 1.0e-6) {
Expand Down
68 changes: 29 additions & 39 deletions src/core/Rational.pm
Expand Up @@ -46,12 +46,9 @@ my role Rational[::NuT = Int, ::DeT = ::("NuT")] does Real {

method nude() { self.REDUCE-ME; $!numerator, $!denominator }
method Num() {
nqp::istype($!numerator,Int)
?? nqp::p6box_n(nqp::div_In(
nqp::decont($!numerator),
nqp::decont($!denominator)
))
!! $!numerator
nqp::p6box_n(nqp::div_In(
nqp::decont($!numerator),
nqp::decont($!denominator)))
}

method floor(Rational:D:) {
Expand All @@ -75,10 +72,8 @@ my role Rational[::NuT = Int, ::DeT = ::("NuT")] does Real {
}
method Bridge() { self.Num }
method Range(::?CLASS:U:) { Range.new(-Inf, Inf) }
method isNaN {
nqp::p6bool(
nqp::isfalse(self.numerator) && nqp::isfalse(self.denominator)
)
method isNaN (--> Bool:D) {
nqp::p6bool(nqp::isfalse($!denominator) && nqp::isfalse($!numerator))
}

method is-prime(--> Bool:D) {
Expand All @@ -87,38 +82,33 @@ my role Rational[::NuT = Int, ::DeT = ::("NuT")] does Real {
}

multi method Str(::?CLASS:D:) {
if nqp::istype($!numerator,Int) {
my $whole = self.abs.floor;
my $fract = self.abs - $whole;

# fight floating point noise issues RT#126016
if $fract.Num == 1e0 { ++$whole; $fract = 0 }

my $result = nqp::if(
nqp::islt_I($!numerator, 0), '-', ''
) ~ $whole;

if $fract {
my $precision = $!denominator < 100_000
?? 6 !! $!denominator.Str.chars + 1;

my $fract-result = '';
while $fract and $fract-result.chars < $precision {
$fract *= 10;
given $fract.floor {
$fract-result ~= $_;
$fract -= $_;
}
}
++$fract-result if 2*$fract >= 1; # round off fractional result
my $whole = self.abs.floor;
my $fract = self.abs - $whole;

$result ~= '.' ~ $fract-result;
# fight floating point noise issues RT#126016
if $fract.Num == 1e0 { ++$whole; $fract = 0 }

my $result = nqp::if(
nqp::islt_I($!numerator, 0), '-', ''
) ~ $whole;

if $fract {
my $precision = $!denominator < 100_000
?? 6 !! $!denominator.Str.chars + 1;

my $fract-result = '';
while $fract and $fract-result.chars < $precision {
$fract *= 10;
given $fract.floor {
$fract-result ~= $_;
$fract -= $_;
}
}
$result
}
else {
$!numerator.Str
++$fract-result if 2*$fract >= 1; # round off fractional result

$result ~= '.' ~ $fract-result;
}
$result
}

method base($base, Any $digits? is copy) {
Expand Down

0 comments on commit 042cb74

Please sign in to comment.