Mercurial > hg > Others > Rakudo
view src/core.c/Rational.pm6 @ 0:c341f82e7ad7 default tip
Rakudo branch in cr.ie.u-ryukyu.ac.jp
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Thu, 26 Dec 2019 16:50:27 +0900 |
parents | |
children |
line wrap: on
line source
# stub of this role is also present in Numeric.pm6; be sure to update # definition there as well, if changing this one my role Rational[::NuT = Int, ::DeT = ::("NuT")] does Real { has NuT $.numerator; has DeT $.denominator; multi method WHICH(Rational:D: --> ValueObjAt:D) { nqp::box_s( nqp::concat( nqp::if( nqp::eqaddr(self.WHAT,Rational), 'Rational|', nqp::concat(nqp::unbox_s(self.^name), '|') ), nqp::concat( nqp::tostr_I($!numerator), nqp::concat('/', nqp::tostr_I($!denominator)) ) ), ValueObjAt ) } method new(NuT:D \nu = 0, DeT:D \de = 1) { my \object := nqp::create(self); nqp::if( de, nqp::stmts( # normal rational (my \gcd := nqp::gcd_I(nqp::decont(nu), nqp::decont(de), Int)), (my \numerator := nqp::div_I(nqp::decont(nu), gcd, NuT)), (my \denominator := nqp::div_I(nqp::decont(de), gcd, DeT)), nqp::if( nqp::islt_I(denominator,0), # need to switch sign? nqp::stmts( # yup, so switch nqp::bindattr( object,::?CLASS,'$!numerator',nqp::neg_I(numerator,Int) ), nqp::p6bindattrinvres( object,::?CLASS,'$!denominator',nqp::neg_I(denominator,Int) ) ), nqp::stmts( # no, so just store nqp::bindattr( object,::?CLASS,'$!numerator',numerator ), nqp::p6bindattrinvres( object,::?CLASS,'$!denominator',denominator ) ) ) ), nqp::stmts( # Inf / NaN nqp::bindattr(object,::?CLASS,'$!numerator', nqp::box_i( nqp::isgt_I(nqp::decont(nu),0) || nqp::neg_i(nqp::istrue(nu)), nu.WHAT ) ), nqp::p6bindattrinvres(object,::?CLASS,'$!denominator', nqp::decont(de) ) ) ) } method nude() { $!numerator, $!denominator } method Num(--> Num:D) { nqp::p6box_n(nqp::div_In($!numerator,$!denominator)) } method floor(Rational:D: --> Int:D) { $!denominator ?? $!denominator == 1 ?? $!numerator !! $!numerator div $!denominator !! Failure.new( X::Numeric::DivideByZero.new( :details('when calling .floor on Rational') ) ) } method ceiling(Rational:D: --> Int:D) { $!denominator ?? $!denominator == 1 ?? $!numerator !! ($!numerator div $!denominator + 1) !! Failure.new( X::Numeric::DivideByZero.new( :details('when calling .ceiling on Rational') ) ) } method Int(--> Int:D) { $!denominator ?? self.truncate !! Failure.new( X::Numeric::DivideByZero.new( :details('when coercing Rational to Int') ) ) } multi method Bool(::?CLASS:D:) { nqp::hllbool(nqp::istrue($!numerator)) } method Bridge() { self.Num } method Range(::?CLASS:U:) { Range.new(-Inf, Inf) } method isNaN (--> Bool:D) { nqp::hllbool(nqp::isfalse($!denominator) && nqp::isfalse($!numerator)) } method is-prime(--> Bool:D) { nqp::if($!denominator == 1,$!numerator.is-prime) } multi method Str(::?CLASS:D: --> Str:D) { nqp::if( $!denominator, nqp::stmts( (my $abs := self.abs), (my $whole := $abs.floor), (my $fract := $abs - $whole), nqp::if( $fract, self!SLOW-STR($whole,$fract), nqp::if( nqp::islt_I($!numerator,0), nqp::concat("-",nqp::tostr_I($whole)), nqp::tostr_I($whole) ) ) ), X::Numeric::DivideByZero.new( :details('when coercing Rational to Str') ).throw ) } method !SLOW-STR(\whole, \fract) { # fight floating point noise issues RT#126016 fract.Num == 1e0 && nqp::eqaddr(self.WHAT,Rat) ?? nqp::islt_I($!numerator,0) ?? nqp::concat("-",nqp::tostr_I(whole + 1)) !! nqp::tostr_I(whole + 1) !! self!STRINGIFY( whole, fract, nqp::eqaddr(self.WHAT,Rat) # Stringify Rats to at least 6 significant digits. There does not # appear to be any written spec for this but there are tests in # roast that specifically test for 6 digits. ?? $!denominator < 100_000 ?? 6 !! (nqp::chars($!denominator.Str) + 1) # TODO v6.d FatRats are tested in roast to have a minimum # precision pf 6 decimal places - mostly due to there being no # formal spec and the desire to test SOMETHING. With this # speed increase, 16 digits would work fine; but it isn't spec. # !! $!denominator < 1_000_000_000_000_000 # ?? 16 !! $!denominator < 100_000 ?? 6 !! (nqp::chars($!denominator.Str) + nqp::chars(whole.Str) + 1 ) ) } method !STRINGIFY(\whole, \fract, Int:D $precision) { my $f := (fract * nqp::pow_I(10, $precision, Num, Int)).round; my $fc = nqp::chars($f.Str); $f := $f div 10 while $f %% 10; # Remove trailing zeros (nqp::isle_I($!numerator,0) ?? "-" !! "") ~ whole ~ '.' ~ '0' x ($precision - $fc) ~ $f } method base($base, Any $digits? is copy) { # XXX TODO: this $base check can be delegated to Int.base once Num/0 gives Inf/NaN, # instead of throwing (which happens in the .log() call before we reach Int.base 2 <= $base <= 36 or fail X::OutOfRange.new( what => "base argument to base", :got($base), :range<2..36>); my $prec; if $digits ~~ Whatever { $digits = Nil; $prec = 2**63; } elsif $digits.defined { $digits = $digits.Int; if $digits > 0 { $prec = $digits; } elsif $digits == 0 { return self.round.base($base) } else { fail X::OutOfRange.new( :what('digits argument to base'), :got($digits), :range<0..^Inf>, ) } } else { # Limit log calculation to 10**307 or less. # log coerces to Num. When larger than 10**307, it overflows and # returns Inf. my $lim = 10**307; if $!denominator < $lim { $prec = ($!denominator < $base**6 ?? 6 !! $!denominator.log($base).ceiling + 1); } else { # If the internal log method is modified to handle larger numbers, # this branch can be modified/removed. my $d = $!denominator; my $exp = 0; ++$exp while ($d div= $base) > $lim; $prec = $exp + $d.log($base).ceiling + 2; } } my $sign = nqp::if( nqp::islt_I($!numerator, 0), '-', '' ); 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 = $sign ~ $whole.base($base); my @conversion := <0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z>; my @fract-digits; while @fract-digits < $prec and ($digits // $fract) { $fract *= $base; my $digit = $fract.floor; push @fract-digits, $digit; $fract -= $digit; } # Round the final number, based on the remaining fractional part if 2*$fract >= 1 { for @fract-digits - 1 ... 0 -> $n { last if ++@fract-digits[$n] < $base; @fract-digits[$n] = 0; $result = $sign ~ ($whole+1).base($base) if $n == 0; } } $result ~ (@fract-digits ?? $base <= 10 ?? '.' ~ @fract-digits.join !! '.' ~ @conversion[@fract-digits].join !! '') } method base-repeating($base = 10) { return ~self, '' if self.narrow ~~ Int; my @quotients; my @remainders; my %remainders; push @quotients, [div] my ($nu, $de) = abs(self).nude; loop { push @remainders, $nu %= $de; last if %remainders{$nu}++ or $nu == 0; $nu *= $base; push @quotients, $nu div $de; } @quotients .= map(*.base($base)); my @cycle = $nu ?? splice @quotients, @remainders.first($nu,:k) + 1 !! (); splice @quotients, 1, 0, '.'; '-' x (self < 0) ~ @quotients.join, @cycle.join; } method succ { self.new($!numerator + $!denominator, $!denominator); } method pred { self.new($!numerator - $!denominator, $!denominator); } method norm() { self } method narrow(::?CLASS:D:) { $!denominator == 1 ?? $!numerator !! self; } multi method round(::?CLASS:D: --> Int:D) { $!denominator ?? nqp::div_I( nqp::add_I(nqp::mul_I($!numerator, 2, Int), $!denominator, Int), nqp::mul_I($!denominator, 2, Int), Int ) !! Failure.new( X::Numeric::DivideByZero.new( :details('when calling .round on Rational') ) ) } } # vim: ft=perl6 expandtab sw=4