<commit_before># 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
    }

    proto method base(|) {*}
    # Convert to given base with sensible number of digits depending on value
    multi method base(Rational:D: Int:D $base --> Str:D) {
        if 2 <= $base <= 36 {
            # Limit log calculation to 10**307 or less.
            # log coerces to Num. When larger than 10**307, it overflows and
            # returns Inf.
            my constant $lim = 10**307;
            if $!denominator < $lim {
                self!base(
                  $base,
                  $!denominator < $base**6
                    ?? 6
                    !! $!denominator.log($base).ceiling + 1,
                    0
                )
            }
            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;
                self!base($base, $exp + $d.log($base).ceiling + 2, 0)
            }<commit_msg>Make Rational.Str a few % faster with 20% fewer allocations<commit_after># 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) {
        if $!denominator {
            my $abs   := self.abs;                              # N / D
            my $whole := $abs.floor;
            my $fract := $abs - $whole;
            $fract
              # fight floating point noise issues RT#126016
              ?? $fract.Num == 1e0 && nqp::eqaddr(self.WHAT,Rat) # 42.666?
                ?? nqp::islt_I($!numerator,0)                    # next Int
                  ?? nqp::concat("-",nqp::tostr_I($whole + 1))   # < 0
                  !! nqp::tostr_I($whole + 1)                    # >= 0
                !! self!STRINGIFY($whole, $fract,                # 42.666
                     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.
                       ?? nqp::islt_I($!denominator,100_000)
                         ?? 6
                         !! (nqp::chars(nqp::tostr_I($!denominator)) + 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(nqp::tostr_I($!denominator))
                              + nqp::chars(nqp::tostr_I($whole))
                              + 1
                            )
                   )
              !! nqp::islt_I($!numerator,0)                      # no fract val
                ?? nqp::concat("-",nqp::tostr_I($whole))         # < 0
                !! nqp::tostr_I($whole)                          # >= 0
        }
        else {                                                   # N / 0
              X::Numeric::DivideByZero.new(
                :details('when coercing Rational to Str')
              ).throw
        }
    }

    method !STRINGIFY(\whole, \fract, int $digits) {
        my $result := nqp::list_s;
        nqp::push_s($result,'-') if nqp::isle_I($!numerator,0);
        nqp::push_s($result,nqp::tostr_I(whole));

        my str $s = nqp::tostr_I(
          (fract * nqp::pow_I(10,$digits,Num,Int)).round
        );
        $s = nqp::concat(nqp::x('0',$digits - nqp::chars($s)),$s)
          if nqp::chars($s) < $digits;

        my int $i = nqp::chars($s);
        nqp::while(
          nqp::eqat($s,'0',$i - 1) && --$i > 0,
          nqp::null
        );

        if $i {
            nqp::push_s($result,'.');
            nqp::push_s($result,nqp::substr($s,0,$i));
        }

        nqp::join('',$result)
    }

    proto method base(|) {*}
    # Convert to given base with sensible number of digits depending on value
    multi method base(Rational:D: Int:D $base --> Str:D) {
        if 2 <= $base <= 36 {
            # Limit log calculation to 10**307 or less.
            # log coerces to Num. When larger than 10**307, it overflows and
            # returns Inf.
            my constant $lim = 10**307;
            if $!denominator < $lim {
                self!base(
                  $base,
                  $!denominator < $base**6
                    ?? 6
                    !! $!denominator.log($base).ceiling + 1,
                    0
                )
            }
            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;
                self!base($base, $exp + $d.log($base).ceiling + 2, 0)
            }
{'fraction_non_alphanumeric': 0.14251223928643283, 'fraction_numerical': 0.014284756220240091, 'mean_word_length': 1.3398713321826456, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4404910', 'n_tokens_mistral': 4911, 'n_tokens_neox': 4540, 'n_words': 934}