1. my role Rational[::NuT, ::DeT] does Real {
  2. has NuT $.numerator = 0;
  3. has DeT $.denominator = 1;
  4. multi method WHICH(Rational:D:) {
  5. nqp::box_s(
  6. nqp::concat(
  7. nqp::if(
  8. nqp::eqaddr(self.WHAT,Rational),
  9. 'Rational|',
  10. nqp::concat(nqp::unbox_s(self.^name), '|')
  11. ),
  12. nqp::concat(
  13. nqp::tostr_I($!numerator),
  14. nqp::concat('/', nqp::tostr_I($!denominator))
  15. )
  16. ),
  17. ObjAt
  18. )
  19. }
  20. method new(NuT \nu = 0, DeT \de = 1) {
  21. my $new := nqp::create(self);
  22. # 0 denominator take it verbatim to support Inf/-Inf/NaN
  23. if de == 0 {
  24. nqp::bindattr($new,::?CLASS,'$!numerator', nqp::decont(nu));
  25. nqp::bindattr($new,::?CLASS,'$!denominator',nqp::decont(de));
  26. }
  27. # normalize
  28. else {
  29. my $gcd := nu gcd de;
  30. my $numerator = nu div $gcd;
  31. my $denominator = de div $gcd;
  32. if $denominator < 0 {
  33. $numerator = -$numerator;
  34. $denominator = -$denominator;
  35. }
  36. nqp::bindattr($new,::?CLASS,'$!numerator', nqp::decont($numerator));
  37. nqp::bindattr($new,::?CLASS,'$!denominator',nqp::decont($denominator));
  38. }
  39. $new
  40. }
  41. method nude() { self.REDUCE-ME; $!numerator, $!denominator }
  42. method Num() {
  43. nqp::istype($!numerator,Int)
  44. ?? nqp::p6box_n(nqp::div_In(
  45. nqp::decont($!numerator),
  46. nqp::decont($!denominator)
  47. ))
  48. !! $!numerator
  49. }
  50. method floor(Rational:D:) {
  51. $!denominator == 1
  52. ?? $!numerator
  53. !! $!numerator div $!denominator
  54. }
  55. method ceiling(Rational:D:) {
  56. self.REDUCE-ME;
  57. $!denominator == 1
  58. ?? $!numerator
  59. !! ($!numerator div $!denominator + 1)
  60. }
  61. method Int() {
  62. $!denominator
  63. ?? self.truncate
  64. !! fail X::Numeric::DivideByZero.new:
  65. :details('when coercing Rational to Int')
  66. }
  67. method Bridge() { self.Num }
  68. method Range(::?CLASS:U:) { Range.new(-Inf, Inf) }
  69. method isNaN {
  70. nqp::p6bool(
  71. nqp::isfalse(self.numerator) && nqp::isfalse(self.denominator)
  72. )
  73. }
  74. multi method Str(::?CLASS:D:) {
  75. if nqp::istype($!numerator,Int) {
  76. my $whole = self.abs.floor;
  77. my $fract = self.abs - $whole;
  78. # fight floating point noise issues RT#126016
  79. if $fract.Num == 1e0 { $whole++; $fract = 0 }
  80. my $result = nqp::if(
  81. nqp::islt_I($!numerator, 0), '-', ''
  82. ) ~ $whole;
  83. if $fract {
  84. my $precision = $!denominator < 100_000
  85. ?? 6 !! $!denominator.Str.chars + 1;
  86. my $fract-result = '';
  87. while $fract and $fract-result.chars < $precision {
  88. $fract *= 10;
  89. given $fract.floor {
  90. $fract-result ~= $_;
  91. $fract -= $_;
  92. }
  93. }
  94. $fract-result++ if 2*$fract >= 1; # round off fractional result
  95. $result ~= '.' ~ $fract-result;
  96. }
  97. $result
  98. }
  99. else {
  100. $!numerator.Str
  101. }
  102. }
  103. method base($base, Any $digits? is copy) {
  104. # XXX TODO: this $base check can be delegated to Int.base once Num/0 gives Inf/NaN,
  105. # instead of throwing (which happens in the .log() call before we reach Int.base
  106. 2 <= $base <= 36 or Failure.new(X::OutOfRange.new(
  107. what => "base argument to base", :got($base), :range<2..36>)
  108. );
  109. my $prec;
  110. if $digits ~~ Whatever {
  111. $digits = Nil;
  112. $prec = 2**63;
  113. }
  114. elsif $digits.defined {
  115. $digits = $digits.Int;
  116. if $digits > 0 {
  117. $prec = $digits;
  118. }
  119. elsif $digits == 0 {
  120. return self.round.base($base)
  121. }
  122. else {
  123. fail X::OutOfRange.new(
  124. :what('digits argument to base'), :got($digits),
  125. :range<0..^Inf>,
  126. )
  127. }
  128. }
  129. else {
  130. $prec = ($!denominator < $base**6 ?? 6 !! $!denominator.log($base).ceiling + 1);
  131. }
  132. my $sign = nqp::if( nqp::islt_I($!numerator, 0), '-', '' );
  133. my $whole = self.abs.floor;
  134. my $fract = self.abs - $whole;
  135. # fight floating point noise issues RT#126016
  136. if $fract.Num == 1e0 { $whole++; $fract = 0 }
  137. my $result = $sign ~ $whole.base($base);
  138. my @conversion := <0 1 2 3 4 5 6 7 8 9
  139. A B C D E F G H I J
  140. K L M N O P Q R S T
  141. U V W X Y Z>;
  142. my @fract-digits;
  143. while @fract-digits < $prec and ($digits // $fract) {
  144. $fract *= $base;
  145. my $digit = $fract.floor;
  146. push @fract-digits, $digit;
  147. $fract -= $digit;
  148. }
  149. # Round the final number, based on the remaining fractional part
  150. if 2*$fract >= 1 {
  151. for @fract-digits-1 ... 0 -> $n {
  152. last if ++@fract-digits[$n] < $base;
  153. @fract-digits[$n] = 0;
  154. $result = $sign ~ ($whole+1).base($base) if $n == 0;
  155. }
  156. }
  157. @fract-digits
  158. ?? $result ~ '.' ~ @conversion[@fract-digits].join
  159. !! $result;
  160. }
  161. method base-repeating($base = 10) {
  162. return ~self, '' if self.narrow ~~ Int;
  163. my @quotients;
  164. my @remainders;
  165. my %remainders;
  166. push @quotients, [div] my ($nu, $de) = abs(self).nude;
  167. loop {
  168. push @remainders, $nu %= $de;
  169. last if %remainders{$nu}++ or $nu == 0;
  170. $nu *= $base;
  171. push @quotients, $nu div $de;
  172. }
  173. @quotients.=map(*.base($base));
  174. my @cycle = $nu
  175. ?? splice(@quotients, @remainders.first($nu,:k) + 1)
  176. !! ();
  177. splice @quotients, 1, 0, '.';
  178. '-' x (self < 0) ~ @quotients.join, @cycle.join;
  179. }
  180. method succ {
  181. self.new($!numerator + $!denominator, $!denominator);
  182. }
  183. method pred {
  184. self.new($!numerator - $!denominator, $!denominator);
  185. }
  186. method norm() { self.REDUCE-ME; self }
  187. method narrow(::?CLASS:D:) {
  188. self.REDUCE-ME;
  189. $!denominator == 1
  190. ?? $!numerator
  191. !! self;
  192. }
  193. method REDUCE-ME(--> Nil) {
  194. if $!denominator > 1 {
  195. my $gcd = $!denominator gcd $!numerator;
  196. if $gcd > 1 {
  197. nqp::bindattr(self, self.WHAT, '$!numerator', $!numerator div $gcd);
  198. nqp::bindattr(self, self.WHAT, '$!denominator', $!denominator div $gcd);
  199. }
  200. }
  201. }
  202. }