diff --git a/Src/IronRuby.Tests/RubyTests.cs b/Src/IronRuby.Tests/RubyTests.cs
index d9a76893..9bbd69bb 100644
--- a/Src/IronRuby.Tests/RubyTests.cs
+++ b/Src/IronRuby.Tests/RubyTests.cs
@@ -875,6 +875,7 @@ public Tests(Driver/*!*/ driver) {
Method_OptionalAndKeywordParameters2,
Range_MinMaxInclude1,
Integer_Power1,
+ Rational_CSharp1,
Method_LightFrame1,
Method_LightFrame2,
Hash_SelfKey1,
diff --git a/Src/IronRuby.Tests/Runtime/MiscTests.cs b/Src/IronRuby.Tests/Runtime/MiscTests.cs
index 9b519e7e..4b6c911b 100644
--- a/Src/IronRuby.Tests/Runtime/MiscTests.cs
+++ b/Src/IronRuby.Tests/Runtime/MiscTests.cs
@@ -3171,6 +3171,81 @@ p r
");
}
+ ///
+ /// Rational as a C# type (RubyRational) with its arithmetic, comparison, #to_f and #hash in C#, and
+ /// Kernel#Rational: Integer, Float, Rational and big operands, coercion, division by zero, NaN,
+ /// Marshal, hash keys, the conversions and errors left to rational.rb. The expected output is CRuby's.
+ ///
+ public void Rational_CSharp1() {
+ TestOutput(@"
+require 'ruby4'
+def t(l); r = begin; yield; rescue => e; [e.class, e.message]; end; puts ""#{l}: #{r.inspect}""; end
+a = Rational(1, 3); b = Rational(-2, 5); big = Rational(2**70, 3); c = Rational(5)
+t('add') { [a + b, a + 1, a + 2**70, a + 0.5, a + big, big + big, a - b, a - 3, b - a, c - c] }
+t('mul') { [a * b, a * 3, a * 0, a * 0.5, big * big, b * -1, a * Rational(3, 1)] }
+t('div') { [a / b, a / 3, a / 0.5, a.quo(b), Rational(1) / a, big / a, a / -2, a / Rational(-1, 2)] }
+t('div0') { a / 0 }
+t('div0r') { a / Rational(0) }
+t('div0f') { a / 0.0 }
+t('cmp') { [a <=> b, a <=> 1, a <=> a, a <=> 0.25, a <=> Float::NAN, a <=> ""x"", big <=> a, c <=> 5] }
+t('eq') { [a == Rational(2, 6), a == b, c == 5, Rational(0) == 0, a == 1.0/3, a == ""x"", Rational(0) == 0.0, big == big] }
+t('to_f') { [a.to_f, b.to_f, big.to_f, Rational(10**400, 3*10**399).to_f, Rational(1, 10**400).to_f] }
+t('hash') { [a.hash == Rational(2, 6).hash, {a => 1}[Rational(1, 3)], {big => 2}[Rational(2**70, 3)], a.eql?(Rational(1, 3)), a.hash == b.hash] }
+o = Object.new; def o.coerce(x) = [x, Rational(1, 2)]
+t('coerce') { [a + o, a * o, a <=> o] }
+t('bad') { a + ""x"" }
+t('int ops') { [1 + a, 2 * a, 3 - a, 4 / a, 1.5 + a, 2**70 + a, 1 <=> a, 1 == Rational(1)] }
+t('misc') { [a.numerator, a.denominator, -a, a.abs, a ** 2, a.round, a.floor, a.ceil, a.truncate, a.to_s, a.inspect, a.to_i, a.zero?, a.positive?] }
+t('marshal') { [Marshal.load(Marshal.dump(a)), Marshal.load(Marshal.dump(big)), a.instance_variables, a.frozen?] }
+t('sum') { (1..10).sum { |i| Rational(1, i) } }
+t('ints') { [Rational(1, 3), Rational(2, 4), Rational(-2, -4), Rational(2, -4), Rational(0, 5), Rational(5), Rational(-5), Rational(2**70, 2**68), Rational(6, 2**70)] }
+t('zero') { Rational(1, 0) }
+t('rat') { [Rational(Rational(1, 2)), Rational(Rational(1, 2), 3), Rational(Rational(1, 2), 1), Rational(1, Rational(1, 2))] }
+t('str') { [Rational(""1/3""), Rational(""0.75""), Rational(""x"", exception: false), Rational(1.5), Rational(nil, exception: false)] }
+t('nil') { Rational(nil) }
+t('nil2') { Rational(1, nil) }
+t('arity0') { Rational() }
+t('arity3') { Rational(1, 2, 3) }
+t('arity4') { Rational(1, 2, 3, 4) }
+t('kw') { [Rational(1, 2, exception: false), Rational(""1/2"", exception: true)] }
+t('badkw') { Rational(1, exception: 1) }
+t('Kernel.') { Kernel.Rational(3, 9) }
+t('arity') { method(:Rational).arity }
+t('class') { [Rational(1, 3).class, Rational(4, 2).denominator.class] }
+", @"
+add: [(-1/15), (4/3), (3541774862152233910273/3), 0.8333333333333333, (1180591620717411303425/3), (2361183241434822606848/3), (11/15), (-8/3), (-11/15), (0/1)]
+mul: [(-2/15), (1/1), (0/1), 0.16666666666666666, (1393796574908163946345982392040522594123776/9), (2/5), (1/1)]
+div: [(-5/6), (1/9), 0.6666666666666666, (-5/6), (3/1), (1180591620717411303424/1), (-1/6), (-2/3)]
+div0: [ZeroDivisionError, ""divided by 0""]
+div0r: [ZeroDivisionError, ""divided by 0""]
+div0f: Infinity
+cmp: [1, -1, 0, 1, nil, nil, 1, 0]
+eq: [true, false, true, true, true, false, true, true]
+to_f: [0.3333333333333333, -0.4, 3.935305402391371e+20, 3.3333333333333335, 0.0]
+hash: [true, 1, 2, true, false]
+coerce: [(5/6), (1/6), -1]
+bad: [TypeError, ""String can't be coerced into Rational""]
+int ops: [(4/3), (2/3), (8/3), (12/1), 1.8333333333333333, (3541774862152233910273/3), 1, true]
+misc: [1, 3, (-1/3), (1/3), (1/9), 0, 0, 1, 0, ""1/3"", ""(1/3)"", 0, false, true]
+marshal: [(1/3), (1180591620717411303424/3), [], true]
+sum: (7381/2520)
+ints: [(1/3), (1/2), (1/2), (-1/2), (0/1), (5/1), (-5/1), (4/1), (3/590295810358705651712)]
+zero: [ZeroDivisionError, ""divided by 0""]
+rat: [(1/2), (1/6), (1/2), (2/1)]
+str: [(1/3), (3/4), nil, (3/2), nil]
+nil: [TypeError, ""can't convert nil into Rational""]
+nil2: [TypeError, ""can't convert nil into Rational""]
+arity0: [ArgumentError, ""wrong number of arguments (given 0, expected 1..2)""]
+arity3: [ArgumentError, ""wrong number of arguments (given 3, expected 1..2)""]
+arity4: [ArgumentError, ""wrong number of arguments (given 4, expected 1..2)""]
+kw: [(1/2), (1/2)]
+badkw: [ArgumentError, ""expected true or false as exception: 1""]
+Kernel.: (1/3)
+arity: -1
+class: [Rational, Integer]
+");
+ }
+
///
/// Integer#** in C# with what it hands rational.rb (__ir_pow_slow__): a negative exponent (a Rational),
/// a negative base to a fractional power (a Complex), coercion, and MRI's answers for 0 ** NaN and
diff --git a/Src/Libraries/Builtins/KernelOps.cs b/Src/Libraries/Builtins/KernelOps.cs
index 2170bc61..98eccf4b 100644
--- a/Src/Libraries/Builtins/KernelOps.cs
+++ b/Src/Libraries/Builtins/KernelOps.cs
@@ -625,18 +625,72 @@ private static bool TryParseRubyInteger(string/*!*/ str, int requestedRadix, out
return site.Target(site, scope.RubyContext.KernelModule, numerator, denominator);
}
+ // Kernel#Rational(x[, y], exception: true) (MRI's nurat_f_rational): Integers, and a Rational
+ // over an Integer, are answered here; anything else - Strings, Floats, objects with #to_r,
+ // the exception: keyword - is rational.rb's __ir_Rational_slow__. Its Ruby `*args,
+ // exception:' signature put every call on the slow path: Rational(1, 3) cost ~0.55 us.
+
+ [RubyMethod("Rational", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Rational", RubyMethodAttributes.PublicSingleton)]
+ public static object ToRational(CallSiteStorage>/*!*/ slow, RubyContext/*!*/ context,
+ object self, object value) {
+ RubyClass cls;
+ if (RubyRational.IsIntegerValue(value) && (cls = GetRationalClass(context)) != null) {
+ return new RubyRational(cls, value, ScriptingRuntimeHelpers.Int32ToObject(1));
+ }
+ if (value is RubyRational) {
+ return value;
+ }
+ var site = slow.GetCallSite("__ir_Rational_slow__", 1);
+ return site.Target(site, self, value);
+ }
+
+ [RubyMethod("Rational", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Rational", RubyMethodAttributes.PublicSingleton)]
+ public static object ToRational(CallSiteStorage>/*!*/ slow, RubyContext/*!*/ context,
+ object self, object numerator, object denominator) {
+ RubyClass cls;
+ if (RubyRational.IsIntegerValue(numerator) && RubyRational.IsIntegerValue(denominator) &&
+ (cls = GetRationalClass(context)) != null) {
+ return RubyRational.Canonicalize(cls, numerator, denominator);
+ }
+ var site = slow.GetCallSite("__ir_Rational_slow__", 2);
+ return site.Target(site, self, numerator, denominator);
+ }
+
+ [RubyMethod("Rational", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Rational", RubyMethodAttributes.PublicSingleton)]
+ public static object ToRational(CallSiteStorage>/*!*/ slow,
+ object self, object numerator, object denominator, object options) {
+ var site = slow.GetCallSite("__ir_Rational_slow__", 3);
+ return site.Target(site, self, numerator, denominator, options);
+ }
+
+ [RubyMethod("Rational", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Rational", RubyMethodAttributes.PublicSingleton)]
+ public static object ToRational(object self, object a, object b, object c, object d, params object[]/*!*/ more) {
+ throw ConversionArity(4 + more.Length, "1..2");
+ }
+
+ // MRI's Rational is a C function of any arity (-1), which reports a missing argument itself
+ [RubyMethod("Rational", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Rational", RubyMethodAttributes.PublicSingleton)]
+ public static object ToRational(object self) {
+ throw ConversionArity(0, "1..2");
+ }
+
// The Rational class of the context that last asked (one per process in practice).
private static RubyClass _rationalClass;
- private static RubyClass GetRationalClass(RubyContext/*!*/ context) {
+ internal static RubyClass GetRationalClass(RubyContext/*!*/ context) {
RubyClass cls = _rationalClass;
if (cls != null && cls.Context == context) {
return cls;
}
object value;
cls = context.ObjectClass.TryGetConstant(null, "Rational", out value) ? value as RubyClass : null;
- // rational.rb's Rational: a Ruby subclass of Numeric whose instances are plain Numerics
- if (cls == null || cls.IsSingletonClass || cls.Name != "Rational" || cls.GetUnderlyingSystemType() != typeof(Numeric)) {
+ // the Rational class, whose instances are RubyRationals
+ if (cls == null || cls.IsSingletonClass || cls.Name != "Rational" || cls.GetUnderlyingSystemType() != typeof(RubyRational)) {
return null;
}
_rationalClass = cls;
@@ -655,11 +709,7 @@ internal static object CreateCanonicalRational(RubyContext/*!*/ context, BigInte
if (cls == null) {
return null;
}
- var result = new Numeric(cls);
- context.SetInstanceVariable(result, "@numerator", Protocols.Normalize(numerator));
- context.SetInstanceVariable(result, "@denominator", Protocols.Normalize(denominator));
- result.Freeze();
- return result;
+ return new RubyRational(cls, Protocols.Normalize(numerator), Protocols.Normalize(denominator));
}
#endregion
diff --git a/Src/Libraries/Builtins/RubyRational.cs b/Src/Libraries/Builtins/RubyRational.cs
new file mode 100644
index 00000000..a65f92c6
--- /dev/null
+++ b/Src/Libraries/Builtins/RubyRational.cs
@@ -0,0 +1,431 @@
+/* ****************************************************************************
+ *
+ * Rational: its representation. The methods are rational.rb's (Src/StdLib/ironruby/
+ * ironruby/rational.rb, a port of MRI 4.0's rational.c); what is here is what that
+ * file builds on - the two Integers a Rational is made of, read and made without a
+ * Ruby method call or an instance variable lookup.
+ *
+ * ***************************************************************************/
+
+using System;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using IronRuby.Runtime;
+using IronRuby.Runtime.Calls;
+using Microsoft.Scripting.Runtime;
+
+namespace IronRuby.Builtins {
+
+ ///
+ /// A Rational: two Integers, the denominator positive, frozen from birth (MRI's struct RRational).
+ /// They were @numerator and @denominator of a plain Numeric; fields make one ~10x cheaper to make
+ /// and to read, and leave #instance_variables empty, as in MRI.
+ ///
+ [RubyClass("Rational", Inherits = typeof(Numeric))]
+ public class RubyRational : Numeric {
+ // null until the parts are set: an instance Marshal allocates before #marshal_load runs
+ private object _numerator;
+ private object _denominator;
+
+ public RubyRational(RubyClass/*!*/ cls)
+ : base(cls) {
+ }
+
+ public RubyRational(RubyClass/*!*/ cls, object numerator, object denominator)
+ : base(cls) {
+ _numerator = numerator;
+ _denominator = denominator;
+ Freeze();
+ }
+
+ public object Numerator { get { return _numerator; } }
+ public object Denominator { get { return _denominator; } }
+
+ #region the methods rational.rb builds on
+
+ [RubyMethod("numerator")]
+ public static object GetNumerator(RubyRational/*!*/ self) {
+ return self._numerator;
+ }
+
+ [RubyMethod("denominator")]
+ public static object GetDenominator(RubyRational/*!*/ self) {
+ return self._denominator;
+ }
+
+ ///
+ /// MRI's nurat_s_new_internal: a frozen Rational of exactly these parts, no checks, no
+ /// canonicalisation - the callers have done what they need. rational.rb's constructor.
+ ///
+ [RubyMethod("__new_internal__", RubyMethodAttributes.PublicSingleton)]
+ public static RubyRational/*!*/ NewInternal(RubyClass/*!*/ self, object numerator, object denominator) {
+ return new RubyRational(self, numerator, denominator);
+ }
+
+ ///
+ /// The parts of an instance that has none yet - the one Marshal allocates for #marshal_load -
+ /// which is then frozen. Anything else is frozen already and refuses, as MRI's does.
+ ///
+ [RubyMethod("__ir_set_parts__", RubyMethodAttributes.PrivateInstance)]
+ public static RubyRational/*!*/ SetParts(RubyContext/*!*/ context, RubyRational/*!*/ self, object numerator, object denominator) {
+ if (self.IsFrozen) {
+ throw RubyExceptions.CreateObjectFrozenError(context, self);
+ }
+ self._numerator = numerator;
+ self._denominator = denominator;
+ self.Freeze();
+ return self;
+ }
+
+ #endregion
+
+ #region arithmetic (rational.c's rb_rational_plus, _minus, _mul, _div, f_addsub, f_muldiv)
+
+ // An Integer as the CLR holds one: an Int32, an Int64 past it, a BigInteger past that.
+ private static bool IsInteger(object value) {
+ return value is int || value is long || value is BigInteger;
+ }
+
+ private static BigInteger Big(object value) {
+ if (value is int) { return (int)value; }
+ if (value is long) { return (long)value; }
+ return (BigInteger)value;
+ }
+
+ // the value of an Int32 or Int64 Integer
+ private static bool TryLong(object value, out long result) {
+ if (value is int) { result = (int)value; return true; }
+ if (value is long) { result = (long)value; return true; }
+ result = 0;
+ return false;
+ }
+
+ // small enough that the products and sums below stay inside a long
+ private static bool Small(object value, out long result) {
+ return TryLong(value, out result) && result >= Int32.MinValue && result <= Int32.MaxValue;
+ }
+
+ private static long Gcd(long a, long b) {
+ a = Math.Abs(a);
+ b = Math.Abs(b);
+ while (b != 0) {
+ long t = a % b;
+ a = b;
+ b = t;
+ }
+ return a;
+ }
+
+ private RubyClass/*!*/ RationalClass {
+ get { return ImmediateClass.GetNonSingletonClass(); }
+ }
+
+ // nurat_s_canonicalize_internal_no_reduce
+ private RubyRational/*!*/ NoReduce(long num, long den) {
+ if (den < 0) {
+ num = -num;
+ den = -den;
+ } else if (den == 0) {
+ throw new DivideByZeroException("divided by 0");
+ }
+ return new RubyRational(RationalClass, Protocols.Normalize(num), Protocols.Normalize(den));
+ }
+
+ private RubyRational/*!*/ NoReduce(BigInteger num, BigInteger den) {
+ if (den.Sign < 0) {
+ num = -num;
+ den = -den;
+ } else if (den.IsZero) {
+ throw new DivideByZeroException("divided by 0");
+ }
+ return new RubyRational(RationalClass, Protocols.Normalize(num), Protocols.Normalize(den));
+ }
+
+ // f_addsub
+ private RubyRational/*!*/ AddSub(object anum, object aden, object bnum, object bden, bool plus) {
+ long an, ad, bn, bd;
+ if (Small(anum, out an) && Small(aden, out ad) && Small(bnum, out bn) && Small(bden, out bd)) {
+ long g = Gcd(ad, bd);
+ long a = an * (bd / g);
+ long b = bn * (ad / g);
+ long c = plus ? a + b : a - b;
+ long b2 = ad / g;
+ long g2 = Gcd(c, g);
+ long num = c / g2;
+ long den = (bd / g2) * b2;
+ return NoReduce(num, den);
+ }
+ BigInteger An = Big(anum), Ad = Big(aden), Bn = Big(bnum), Bd = Big(bden);
+ BigInteger G = BigInteger.GreatestCommonDivisor(Ad, Bd);
+ BigInteger A = An * (Bd / G);
+ BigInteger B = Bn * (Ad / G);
+ BigInteger C = plus ? A + B : A - B;
+ BigInteger B2 = Ad / G;
+ BigInteger G2 = BigInteger.GreatestCommonDivisor(C, G);
+ return NoReduce(C / G2, (Bd / G2) * B2);
+ }
+
+ // f_muldiv, the division already turned into a multiplication by the reciprocal
+ private RubyRational/*!*/ MulDiv(object anum, object aden, object bnum, object bden, bool divide) {
+ long an, ad, bn, bd;
+ if (Small(anum, out an) && Small(aden, out ad) && Small(bnum, out bn) && Small(bden, out bd)) {
+ if (divide) {
+ if (bn < 0) {
+ an = -an;
+ bn = -bn;
+ }
+ long t = bn; bn = bd; bd = t;
+ }
+ long g1 = Gcd(an, bd);
+ long g2 = Gcd(ad, bn);
+ if (g1 == 0) { g1 = 1; }
+ if (g2 == 0) { g2 = 1; }
+ return NoReduce((an / g1) * (bn / g2), (ad / g2) * (bd / g1));
+ }
+ BigInteger An = Big(anum), Ad = Big(aden), Bn = Big(bnum), Bd = Big(bden);
+ if (divide) {
+ if (Bn.Sign < 0) {
+ An = -An;
+ Bn = -Bn;
+ }
+ BigInteger t = Bn; Bn = Bd; Bd = t;
+ }
+ BigInteger G1 = BigInteger.GreatestCommonDivisor(An, Bd);
+ BigInteger G2 = BigInteger.GreatestCommonDivisor(Ad, Bn);
+ if (G1.IsZero) { G1 = BigInteger.One; }
+ if (G2.IsZero) { G2 = BigInteger.One; }
+ return NoReduce((An / G1) * (Bn / G2), (Ad / G2) * (Bd / G1));
+ }
+
+ // Anything that is not an Integer, a Float or a Rational: rb_num_coerce_bin, in rational.rb.
+ private static object CoerceBin(CallSiteStorage>/*!*/ coerce, RubyRational/*!*/ self,
+ object other, string/*!*/ op) {
+ var site = coerce.GetCallSite("__ir_num_coerce_bin__", 2);
+ return site.Target(site, self, other, coerce.Context.CreateAsciiSymbol(op));
+ }
+
+ // A Rational operand: the commonest case, as an overload of its own so that the binder can
+ // pick it by the operand's type, without the storage the coercion of anything else needs.
+ [RubyMethod("+")]
+ public static object Add(RubyRational/*!*/ self, [NotNull]RubyRational/*!*/ other) {
+ return self.AddSub(self._numerator, self._denominator, other._numerator, other._denominator, true);
+ }
+
+ [RubyMethod("+")]
+ public static object Add(CallSiteStorage>/*!*/ coerce, RubyRational/*!*/ self, object other) {
+ if (IsInteger(other)) {
+ return self.AddSub(self._numerator, self._denominator, other, 1, true);
+ }
+ if (other is double) {
+ return ToFloat(self) + (double)other;
+ }
+ var r = other as RubyRational;
+ if (r != null) {
+ return self.AddSub(self._numerator, self._denominator, r._numerator, r._denominator, true);
+ }
+ return CoerceBin(coerce, self, other, "+");
+ }
+
+ [RubyMethod("-")]
+ public static object Subtract(CallSiteStorage>/*!*/ coerce, RubyRational/*!*/ self, object other) {
+ if (IsInteger(other)) {
+ return self.AddSub(self._numerator, self._denominator, other, 1, false);
+ }
+ if (other is double) {
+ return ToFloat(self) - (double)other;
+ }
+ var r = other as RubyRational;
+ if (r != null) {
+ return self.AddSub(self._numerator, self._denominator, r._numerator, r._denominator, false);
+ }
+ return CoerceBin(coerce, self, other, "-");
+ }
+
+ [RubyMethod("*")]
+ public static object Multiply(CallSiteStorage>/*!*/ coerce, RubyRational/*!*/ self, object other) {
+ if (IsInteger(other)) {
+ return self.MulDiv(self._numerator, self._denominator, other, 1, false);
+ }
+ if (other is double) {
+ return ToFloat(self) * (double)other;
+ }
+ var r = other as RubyRational;
+ if (r != null) {
+ return self.MulDiv(self._numerator, self._denominator, r._numerator, r._denominator, false);
+ }
+ return CoerceBin(coerce, self, other, "*");
+ }
+
+ [RubyMethod("/"), RubyMethod("quo")]
+ public static object Divide(CallSiteStorage>/*!*/ coerce, RubyRational/*!*/ self, object other) {
+ if (IsInteger(other)) {
+ if (Big(other).IsZero) {
+ throw new DivideByZeroException("divided by 0");
+ }
+ return self.MulDiv(self._numerator, self._denominator, other, 1, true);
+ }
+ if (other is double) {
+ return ToFloat(self) / (double)other;
+ }
+ var r = other as RubyRational;
+ if (r != null) {
+ if (Big(r._numerator).IsZero) {
+ throw new DivideByZeroException("divided by 0");
+ }
+ if (IsOne(self._numerator) && IsOne(self._denominator)) {
+ return self.NoReduce(Big(r._denominator), Big(r._numerator));
+ }
+ return self.MulDiv(self._numerator, self._denominator, r._numerator, r._denominator, true);
+ }
+ return CoerceBin(coerce, self, other, "/");
+ }
+
+ private static bool IsOne(object value) {
+ return value is int && (int)value == 1;
+ }
+
+ #endregion
+
+ #region canonical construction (nurat_s_canonicalize_internal)
+
+ ///
+ /// num/den in lowest terms with the sign on the numerator; ZeroDivisionError for a zero
+ /// denominator. Both must be Integers (Int32, Int64 or BigInteger).
+ ///
+ internal static RubyRational/*!*/ Canonicalize(RubyClass/*!*/ cls, object num, object den) {
+ long n, d;
+ if (Small(num, out n) && Small(den, out d)) {
+ if (d < 0) {
+ n = -n;
+ d = -d;
+ } else if (d == 0) {
+ throw new DivideByZeroException("divided by 0");
+ }
+ if (n != 1 && d != 1) {
+ long g = Gcd(n, d);
+ if (g > 1) {
+ n /= g;
+ d /= g;
+ }
+ }
+ return new RubyRational(cls, Protocols.Normalize(n), Protocols.Normalize(d));
+ }
+ BigInteger N = Big(num), D = Big(den);
+ if (D.Sign < 0) {
+ N = -N;
+ D = -D;
+ } else if (D.IsZero) {
+ throw new DivideByZeroException("divided by 0");
+ }
+ if (!N.IsOne && !D.IsOne) {
+ BigInteger G = BigInteger.GreatestCommonDivisor(N, D);
+ if (G > BigInteger.One) {
+ N /= G;
+ D /= G;
+ }
+ }
+ return new RubyRational(cls, Protocols.Normalize(N), Protocols.Normalize(D));
+ }
+
+ internal static bool IsIntegerValue(object value) {
+ return IsInteger(value);
+ }
+
+ #endregion
+
+ #region <=>, ==, to_f, hash
+
+ // rb_dbl_cmp
+ private static object DoubleCompare(double a, double b) {
+ if (Double.IsNaN(a) || Double.IsNaN(b)) { return null; }
+ return ScriptingRuntimeHelpers.Int32ToObject(a == b ? 0 : a > b ? 1 : -1);
+ }
+
+ // rb_rational_cmp
+ [RubyMethod("<=>")]
+ public static object Compare(CallSiteStorage>/*!*/ coerce, RubyRational/*!*/ self, object other) {
+ object bnum, bden;
+ if (IsInteger(other)) {
+ bnum = other;
+ bden = 1;
+ } else if (other is RubyRational) {
+ bnum = ((RubyRational)other)._numerator;
+ bden = ((RubyRational)other)._denominator;
+ } else if (other is double) {
+ return DoubleCompare(ToFloat(self), (double)other);
+ } else {
+ var site = coerce.GetCallSite("__ir_num_coerce_cmp__", 2);
+ return site.Target(site, self, other, coerce.Context.CreateAsciiSymbol("<=>"));
+ }
+ long an, ad, bn, bd;
+ int sign;
+ if (Small(self._numerator, out an) && Small(self._denominator, out ad) && Small(bnum, out bn) && Small(bden, out bd)) {
+ sign = Math.Sign(an * bd - bn * ad);
+ } else {
+ sign = (Big(self._numerator) * Big(bden) - Big(bnum) * Big(self._denominator)).Sign;
+ }
+ return ScriptingRuntimeHelpers.Int32ToObject(sign);
+ }
+
+ // nurat_eqeq_p
+ [RubyMethod("==")]
+ public static bool Equal(BinaryOpStorage/*!*/ equals, RubyRational/*!*/ self, object other) {
+ if (IsInteger(other)) {
+ if (Big(self._numerator).IsZero && Big(other).IsZero) { return true; }
+ return IsOne(self._denominator) && Big(self._numerator) == Big(other);
+ }
+ if (other is double) {
+ object c = DoubleCompare(ToFloat(self), (double)other);
+ return c is int && (int)c == 0;
+ }
+ var r = other as RubyRational;
+ if (r != null) {
+ if (Big(self._numerator).IsZero && Big(r._numerator).IsZero) { return true; }
+ return Big(self._numerator) == Big(r._numerator) && Big(self._denominator) == Big(r._denominator);
+ }
+ var site = equals.GetCallSite("==");
+ return RubyOps.IsTrue(site.Target(site, other, self));
+ }
+
+ // nurat_to_double: the exact quotient, rounded once. Two doubles that hold their Integers
+ // exactly divide that way; anything larger is Integer#fdiv's (rational.rb's #to_f before).
+ private static readonly long MaxExactDouble = 1L << 53;
+
+ public static double ToFloat(RubyRational/*!*/ self) {
+ long n, d;
+ if (TryLong(self._numerator, out n) && TryLong(self._denominator, out d) &&
+ n > -MaxExactDouble && n < MaxExactDouble && d < MaxExactDouble) {
+ return (double)n / (double)d;
+ }
+ return BigFloatQuotient(Big(self._numerator), Big(self._denominator));
+ }
+
+ [RubyMethod("to_f")]
+ public static object ToF(BinaryOpStorage/*!*/ fdiv, RubyRational/*!*/ self) {
+ long n, d;
+ if (TryLong(self._numerator, out n) && TryLong(self._denominator, out d) &&
+ n > -MaxExactDouble && n < MaxExactDouble && d < MaxExactDouble) {
+ return (double)n / (double)d;
+ }
+ var site = fdiv.GetCallSite("fdiv");
+ return site.Target(site, self._numerator, self._denominator);
+ }
+
+ // what Integer#fdiv answers for two Integers (ClrBigInteger's exact division), for the
+ // arithmetic above that wants a double without a call
+ private static double BigFloatQuotient(BigInteger n, BigInteger d) {
+ return ClrInteger.FDiv(n, d);
+ }
+
+ // nurat_hash: a function of the two parts, the same for any representation of them
+ [RubyMethod("hash")]
+ public static int Hash(RubyRational/*!*/ self) {
+ int h1 = Big(self._numerator).GetHashCode();
+ int h2 = Big(self._denominator).GetHashCode();
+ return unchecked((h1 * 31 + h2) * 31 + 0x52617469);
+ }
+
+ #endregion
+ }
+}
diff --git a/Src/Libraries/Extensions/ClrInteger.cs b/Src/Libraries/Extensions/ClrInteger.cs
index b817fd51..af3cde76 100644
--- a/Src/Libraries/Extensions/ClrInteger.cs
+++ b/Src/Libraries/Extensions/ClrInteger.cs
@@ -390,9 +390,20 @@ public static double Multiply(int self, double other) {
return ClrInteger.PowerNonNegative(self, other);
} else if (self == 1) {
return One;
- } else {
- return PowerSlow(slow, self, other, () => Math.Pow(self, other));
+ } else if (self == -1) {
+ return ScriptingRuntimeHelpers.Int32ToObject((other & 1) == 0 ? 1 : -1);
+ } else if (self != 0 && other != Int32.MinValue) {
+ // rb_int_pow's Rational: 1/(self ** -other), already in lowest terms, the sign moved
+ // onto the numerator (rational.rb's __ir_pow_slow__ makes the same one, slower)
+ RubyClass cls = KernelOps.GetRationalClass(slow.Context);
+ if (cls != null) {
+ // PowerNonNegative refuses a result too large to make, as MRI does
+ object power = PowerNonNegative(new BigInteger(Math.Abs((long)self)), new BigInteger(-(long)other));
+ bool negative = self < 0 && (other & 1) != 0;
+ return new RubyRational(cls, ScriptingRuntimeHelpers.Int32ToObject(negative ? -1 : 1), power);
+ }
}
+ return PowerSlow(slow, self, other, () => Math.Pow(self, other));
}
// a Fixnum to a non-negative Fixnum power (Integer#pow)
diff --git a/Src/Libraries/Initializers.Generated.cs b/Src/Libraries/Initializers.Generated.cs
index 5e78af50..87702146 100644
--- a/Src/Libraries/Initializers.Generated.cs
+++ b/Src/Libraries/Initializers.Generated.cs
@@ -159,7 +159,7 @@ protected override void LoadModules() {
DefineGlobalClass("Enumerator", typeof(IronRuby.Builtins.Enumerator), 0x0000000F, Context.ObjectClass, LoadEnumerator_Instance, null, null, new IronRuby.Builtins.RubyModule[] {def63},
new Func(IronRuby.Builtins.Enumerator.Create)
);
- IronRuby.Builtins.RubyClass def70 = Context.ExceptionClass = DefineGlobalClass("Exception", typeof(System.Exception), 0x00000007, Context.ObjectClass, LoadException_Instance, LoadException_Class, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def71 = Context.ExceptionClass = DefineGlobalClass("Exception", typeof(System.Exception), 0x00000007, Context.ObjectClass, LoadException_Instance, LoadException_Class, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__Exception));
Context.FalseClass = DefineGlobalClass("FalseClass", typeof(IronRuby.Builtins.FalseClass), 0x0000000F, Context.ObjectClass, LoadFalseClass_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray);
#if FEATURE_FILESYSTEM
@@ -181,7 +181,7 @@ protected override void LoadModules() {
DefineGlobalClass("Method", typeof(IronRuby.Builtins.RubyMethod), 0x00000007, Context.ObjectClass, LoadMethod_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray);
// Skipped primitive: Module
Context.NilClass = DefineGlobalClass("NilClass", typeof(Microsoft.Scripting.Runtime.DynamicNull), 0x00000007, Context.ObjectClass, LoadNilClass_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray);
- IronRuby.Builtins.RubyClass def74 = DefineGlobalClass("Numeric", typeof(IronRuby.Builtins.Numeric), 0x0000000F, Context.ObjectClass, LoadNumeric_Instance, null, null, new IronRuby.Builtins.RubyModule[] {def69});
+ IronRuby.Builtins.RubyClass def70 = DefineGlobalClass("Numeric", typeof(IronRuby.Builtins.Numeric), 0x0000000F, Context.ObjectClass, LoadNumeric_Instance, null, null, new IronRuby.Builtins.RubyModule[] {def69});
IronRuby.Builtins.RubyClass def52 = DefineClass("ObjectSpace::WeakMap", typeof(IronRuby.Builtins.WeakMap), 0x0000000F, Context.ObjectClass, LoadObjectSpace__WeakMap_Instance, null, null, new IronRuby.Builtins.RubyModule[] {def63});
DefineGlobalClass("Proc", typeof(IronRuby.Builtins.Proc), 0x00000007, Context.ObjectClass, LoadProc_Instance, LoadProc_Class, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Action(IronRuby.Builtins.ProcOps.Error)
@@ -226,37 +226,38 @@ protected override void LoadModules() {
IronRuby.Builtins.RubyClass def42 = DefineGlobalClass("File", typeof(IronRuby.Builtins.RubyFile), 0x00000007, def45, LoadFile_Instance, LoadFile_Class, LoadFile_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func>, IronRuby.Runtime.ConversionStorage>, IronRuby.Runtime.ConversionStorage, IronRuby.Runtime.ConversionStorage, IronRuby.Builtins.RubyClass, System.Object, System.Object, System.Object, System.Collections.Generic.IDictionary, IronRuby.Builtins.RubyFile>(IronRuby.Builtins.RubyFileOps.CreateFile)
);
- DefineGlobalClass("Float", typeof(System.Double), 0x00000007, def74, LoadFloat_Instance, LoadFloat_Class, LoadFloat_Constants, IronRuby.Builtins.RubyModule.EmptyArray);
- IronRuby.Builtins.RubyClass def73 = DefineGlobalClass("Integer", typeof(System.Int32), 0x00000007, def74, LoadInteger_Instance, LoadInteger_Class, LoadInteger_Constants, IronRuby.Builtins.RubyModule.EmptyArray);
- DefineGlobalClass("NoMemoryError", typeof(System.OutOfMemoryException), 0x00000007, def70, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ DefineGlobalClass("Float", typeof(System.Double), 0x00000007, def70, LoadFloat_Instance, LoadFloat_Class, LoadFloat_Constants, IronRuby.Builtins.RubyModule.EmptyArray);
+ IronRuby.Builtins.RubyClass def74 = DefineGlobalClass("Integer", typeof(System.Int32), 0x00000007, def70, LoadInteger_Instance, LoadInteger_Class, LoadInteger_Constants, IronRuby.Builtins.RubyModule.EmptyArray);
+ DefineGlobalClass("NoMemoryError", typeof(System.OutOfMemoryException), 0x00000007, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__NoMemoryError));
+ DefineGlobalClass("Rational", typeof(IronRuby.Builtins.RubyRational), 0x0000000F, def70, LoadRational_Instance, LoadRational_Class, null, IronRuby.Builtins.RubyModule.EmptyArray);
DefineGlobalClass("Refinement", typeof(IronRuby.Builtins.RefinementOps), 0x0000000F, Context.ModuleClass, LoadRefinement_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray);
- IronRuby.Builtins.RubyClass def68 = DefineGlobalClass("ScriptError", typeof(IronRuby.Builtins.ScriptError), 0x00000007, def70, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def68 = DefineGlobalClass("ScriptError", typeof(IronRuby.Builtins.ScriptError), 0x00000007, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__ScriptError));
- DefineGlobalClass("SecurityError", typeof(System.Security.SecurityException), 0x00000007, def70, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ DefineGlobalClass("SecurityError", typeof(System.Security.SecurityException), 0x00000007, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__SecurityError));
- IronRuby.Builtins.RubyClass def67 = DefineGlobalClass("SignalException", typeof(IronRuby.Builtins.SignalException), 0x0000000F, def70, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def67 = DefineGlobalClass("SignalException", typeof(IronRuby.Builtins.SignalException), 0x0000000F, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__SignalException));
- IronRuby.Builtins.RubyClass def62 = Context.StandardErrorClass = DefineGlobalClass("StandardError", typeof(System.SystemException), 0x00000007, def70, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def62 = Context.StandardErrorClass = DefineGlobalClass("StandardError", typeof(System.SystemException), 0x00000007, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__StandardError));
- ExtendClass(typeof(System.Decimal), 0x00000000, def74, LoadSystem__Decimal_Instance, LoadSystem__Decimal_Class, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.Decimal), 0x00000000, def70, LoadSystem__Decimal_Instance, LoadSystem__Decimal_Class, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.DecimalOps.InducedFrom)
);
- ExtendClass(typeof(System.Single), 0x00000000, def74, LoadSystem__Single_Instance, LoadSystem__Single_Class, LoadSystem__Single_Constants, new IronRuby.Builtins.RubyModule[] {def76},
+ ExtendClass(typeof(System.Single), 0x00000000, def70, LoadSystem__Single_Instance, LoadSystem__Single_Class, LoadSystem__Single_Constants, new IronRuby.Builtins.RubyModule[] {def76},
new Func(IronRuby.Builtins.SingleOps.Create)
);
- DefineGlobalClass("SystemExit", typeof(IronRuby.Builtins.SystemExit), 0x00000007, def70, LoadSystemExit_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ DefineGlobalClass("SystemExit", typeof(IronRuby.Builtins.SystemExit), 0x00000007, def71, LoadSystemExit_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.SystemExitOps.Factory),
new Func(IronRuby.Builtins.SystemExitOps.Factory),
new Func(IronRuby.Builtins.SystemExitOps.Factory)
);
- DefineGlobalClass("SystemStackError", typeof(IronRuby.Builtins.SystemStackError), 0x00000007, def70, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ DefineGlobalClass("SystemStackError", typeof(IronRuby.Builtins.SystemStackError), 0x00000007, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__SystemStackError));
- IronRuby.Builtins.RubyClass def72 = DefineGlobalClass("ArgumentError", typeof(System.ArgumentException), 0x00000007, def62, LoadArgumentError_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def73 = DefineGlobalClass("ArgumentError", typeof(System.ArgumentException), 0x00000007, def62, LoadArgumentError_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__ArgumentError));
IronRuby.Builtins.RubyClass def61 = DefineGlobalClass("EncodingError", typeof(IronRuby.Builtins.EncodingError), 0x00000007, def62, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__EncodingError));
- IronRuby.Builtins.RubyClass def71 = DefineGlobalClass("IndexError", typeof(IronRuby.Builtins.IndexError), 0x00000007, def62, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def72 = DefineGlobalClass("IndexError", typeof(IronRuby.Builtins.IndexError), 0x00000007, def62, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__IndexError));
DefineGlobalClass("Interrupt", typeof(IronRuby.Builtins.Interrupt), 0x0000000F, def67, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__Interrupt));
@@ -279,37 +280,37 @@ protected override void LoadModules() {
new Func(BuiltinsLibraryInitializer.ExceptionFactory__RuntimeError));
DefineGlobalClass("SyntaxError", typeof(IronRuby.Builtins.SyntaxError), 0x00000007, def68, LoadSyntaxError_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__SyntaxError));
- ExtendClass(typeof(System.Byte), 0x00000000, def73, LoadSystem__Byte_Instance, LoadSystem__Byte_Class, LoadSystem__Byte_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.Byte), 0x00000000, def74, LoadSystem__Byte_Instance, LoadSystem__Byte_Class, LoadSystem__Byte_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.ByteOps.InducedFrom),
new Func(IronRuby.Builtins.ByteOps.InducedFrom),
new Func(IronRuby.Builtins.ByteOps.InducedFrom)
);
- ExtendClass(typeof(System.Int16), 0x00000000, def73, LoadSystem__Int16_Instance, LoadSystem__Int16_Class, LoadSystem__Int16_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.Int16), 0x00000000, def74, LoadSystem__Int16_Instance, LoadSystem__Int16_Class, LoadSystem__Int16_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.Int16Ops.InducedFrom),
new Func(IronRuby.Builtins.Int16Ops.InducedFrom),
new Func(IronRuby.Builtins.Int16Ops.InducedFrom)
);
- ExtendClass(typeof(System.Int64), 0x00000000, def73, LoadSystem__Int64_Instance, LoadSystem__Int64_Class, LoadSystem__Int64_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.Int64), 0x00000000, def74, LoadSystem__Int64_Instance, LoadSystem__Int64_Class, LoadSystem__Int64_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.Int64Ops.InducedFrom),
new Func(IronRuby.Builtins.Int64Ops.InducedFrom),
new Func(IronRuby.Builtins.Int64Ops.InducedFrom)
);
- ExtendClass(typeof(System.SByte), 0x00000000, def73, LoadSystem__SByte_Instance, LoadSystem__SByte_Class, LoadSystem__SByte_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.SByte), 0x00000000, def74, LoadSystem__SByte_Instance, LoadSystem__SByte_Class, LoadSystem__SByte_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.SByteOps.InducedFrom),
new Func(IronRuby.Builtins.SByteOps.InducedFrom),
new Func(IronRuby.Builtins.SByteOps.InducedFrom)
);
- ExtendClass(typeof(System.UInt16), 0x00000000, def73, LoadSystem__UInt16_Instance, LoadSystem__UInt16_Class, LoadSystem__UInt16_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.UInt16), 0x00000000, def74, LoadSystem__UInt16_Instance, LoadSystem__UInt16_Class, LoadSystem__UInt16_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.UInt16Ops.InducedFrom),
new Func(IronRuby.Builtins.UInt16Ops.InducedFrom),
new Func(IronRuby.Builtins.UInt16Ops.InducedFrom)
);
- ExtendClass(typeof(System.UInt32), 0x00000000, def73, LoadSystem__UInt32_Instance, LoadSystem__UInt32_Class, LoadSystem__UInt32_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.UInt32), 0x00000000, def74, LoadSystem__UInt32_Instance, LoadSystem__UInt32_Class, LoadSystem__UInt32_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.UInt32Ops.InducedFrom),
new Func(IronRuby.Builtins.UInt32Ops.InducedFrom),
new Func(IronRuby.Builtins.UInt32Ops.InducedFrom)
);
- ExtendClass(typeof(System.UInt64), 0x00000000, def73, LoadSystem__UInt64_Instance, LoadSystem__UInt64_Class, LoadSystem__UInt64_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
+ ExtendClass(typeof(System.UInt64), 0x00000000, def74, LoadSystem__UInt64_Instance, LoadSystem__UInt64_Class, LoadSystem__UInt64_Constants, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.UInt64Ops.InducedFrom),
new Func(IronRuby.Builtins.UInt64Ops.InducedFrom),
new Func(IronRuby.Builtins.UInt64Ops.InducedFrom)
@@ -402,9 +403,9 @@ protected override void LoadModules() {
);
IronRuby.Builtins.RubyClass def57 = DefineClass("Regexp::TimeoutError", typeof(System.Text.RegularExpressions.RegexMatchTimeoutException), 0x00000007, def77, LoadRegexp__TimeoutError_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__Regexp__TimeoutError));
- IronRuby.Builtins.RubyClass def60 = DefineGlobalClass("StopIteration", typeof(IronRuby.Builtins.StopIteration), 0x0000000F, def71, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ IronRuby.Builtins.RubyClass def60 = DefineGlobalClass("StopIteration", typeof(IronRuby.Builtins.StopIteration), 0x0000000F, def72, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(BuiltinsLibraryInitializer.ExceptionFactory__StopIteration));
- DefineGlobalClass("UncaughtThrowError", typeof(IronRuby.Builtins.UncaughtThrowError), 0x00000007, def72, LoadUncaughtThrowError_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
+ DefineGlobalClass("UncaughtThrowError", typeof(IronRuby.Builtins.UncaughtThrowError), 0x00000007, def73, LoadUncaughtThrowError_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
new Func(IronRuby.Builtins.UncaughtThrowErrorOps.Create)
);
DefineGlobalClass("ClosedQueueError", typeof(IronRuby.Builtins.ClosedQueueError), 0x0000000F, def60, null, null, null, IronRuby.Builtins.RubyModule.EmptyArray,
@@ -5197,6 +5198,15 @@ private static void LoadKernel_Instance(IronRuby.Builtins.RubyModule/*!*/ module
new Func, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.Random)
);
+ DefineLibraryMethod(module, "Rational", 0x52,
+ new[] { 0x00000000U, 0x00000000U, 0x00000000U, 0x80000000U, 0x00000000U},
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToRational),
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToRational),
+ new Func>, System.Object, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToRational),
+ new Func(IronRuby.Builtins.KernelOps.ToRational),
+ new Func(IronRuby.Builtins.KernelOps.ToRational)
+ );
+
DefineLibraryMethod(module, "remove_instance_variable", 0x51,
0x00020004U,
new Func(IronRuby.Builtins.KernelOps.RemoveInstanceVariable)
@@ -5587,6 +5597,15 @@ private static void LoadKernel_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
new Func, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.Random)
);
+ DefineLibraryMethod(module, "Rational", 0x61,
+ new[] { 0x00000000U, 0x00000000U, 0x00000000U, 0x80000000U, 0x00000000U},
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToRational),
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToRational),
+ new Func>, System.Object, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToRational),
+ new Func(IronRuby.Builtins.KernelOps.ToRational),
+ new Func(IronRuby.Builtins.KernelOps.ToRational)
+ );
+
DefineLibraryMethod(module, "require", 0x61,
0x00000000U,
new Func, IronRuby.Runtime.RubyScope, System.Object, System.Object, System.Boolean>(IronRuby.Builtins.KernelOps.Require)
@@ -7758,6 +7777,78 @@ private static void LoadRangeError_Instance(IronRuby.Builtins.RubyModule/*!*/ mo
module.HideMethod("message");
}
+ private static void LoadRational_Instance(IronRuby.Builtins.RubyModule/*!*/ module) {
+ DefineLibraryMethod(module, "__ir_set_parts__", 0x52,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.SetParts)
+ );
+
+ DefineLibraryMethod(module, "-", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyRational, System.Object, System.Object>(IronRuby.Builtins.RubyRational.Subtract)
+ );
+
+ DefineLibraryMethod(module, "*", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyRational, System.Object, System.Object>(IronRuby.Builtins.RubyRational.Multiply)
+ );
+
+ DefineLibraryMethod(module, "/", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyRational, System.Object, System.Object>(IronRuby.Builtins.RubyRational.Divide)
+ );
+
+ DefineLibraryMethod(module, "+", 0x51,
+ 0x00000002U, 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.Add),
+ new Func>, IronRuby.Builtins.RubyRational, System.Object, System.Object>(IronRuby.Builtins.RubyRational.Add)
+ );
+
+ DefineLibraryMethod(module, "<=>", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyRational, System.Object, System.Object>(IronRuby.Builtins.RubyRational.Compare)
+ );
+
+ DefineLibraryMethod(module, "==", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.Equal)
+ );
+
+ DefineLibraryMethod(module, "denominator", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.GetDenominator)
+ );
+
+ DefineLibraryMethod(module, "hash", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.Hash)
+ );
+
+ DefineLibraryMethod(module, "numerator", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.GetNumerator)
+ );
+
+ DefineLibraryMethod(module, "quo", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyRational, System.Object, System.Object>(IronRuby.Builtins.RubyRational.Divide)
+ );
+
+ DefineLibraryMethod(module, "to_f", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.ToF)
+ );
+
+ }
+
+ private static void LoadRational_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
+ DefineLibraryMethod(module, "__new_internal__", 0x61,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyRational.NewInternal)
+ );
+
+ }
+
private static void LoadRefinement_Instance(IronRuby.Builtins.RubyModule/*!*/ module) {
DefineLibraryMethod(module, "import_methods", 0x52,
0x80000000U,
diff --git a/Src/StdLib/ironruby/ironruby/rational.rb b/Src/StdLib/ironruby/ironruby/rational.rb
index eced7141..b8ffd869 100644
--- a/Src/StdLib/ironruby/ironruby/rational.rb
+++ b/Src/StdLib/ironruby/ironruby/rational.rb
@@ -7,9 +7,10 @@
# comment says nothing more, the method does what that function does, in the same order,
# because ruby/spec watches which methods a mock operand is sent and in what order.
#
-# A Rational is two Integers in @numerator and @denominator, the denominator positive,
-# frozen from birth. rational.c undefines Rational.new and Rational.allocate; instances
-# are made by __new_internal__ (nurat_s_new_internal), which takes no checks.
+# A Rational is two Integers, the denominator positive, frozen from birth. They are fields
+# of the C# RubyRational (Src/Libraries/Builtins/RubyRational.cs), read by #numerator and
+# #denominator. rational.c undefines Rational.new and Rational.allocate; instances are made
+# by __new_internal__ (nurat_s_new_internal, C#), which takes no checks.
class Numeric
# rb_num_coerce_bin: other.coerce(self), then the operator on the pair.
@@ -69,24 +70,10 @@ def __ir_coerce_name__(value)
class Rational < Numeric
class << self
- alias_method :__ir_allocate__, :allocate
- private :__ir_allocate__
undef_method :allocate
undef_method :new
end
- # nurat_s_new_internal: no canonicalisation, no checks; OBJ_FREEZE.
- def self.__new_internal__(num, den)
- __ir_allocate__.__send__(:__ir_set_parts__, num, den)
- end
-
- def __ir_set_parts__(num, den)
- @numerator = num
- @denominator = den
- freeze
- end
- private :__ir_set_parts__
-
# rb_rational_raw: Integers (via #to_int) with the sign moved onto the numerator.
def self.__raw__(num, den)
num = ::Kernel.__ir_to_int__(num) unless ::Integer === num
@@ -168,11 +155,10 @@ def self.__dbl_cmp__(a, b)
a > b ? 1 : -1
end
- attr_reader :numerator, :denominator
# rb_rational_uminus
def -@
- ::Rational.__canonicalize__(-@numerator, @denominator)
+ ::Rational.__canonicalize__(-numerator, denominator)
end
# f_addsub
@@ -190,31 +176,9 @@ def __ir_addsub__(anum, aden, bnum, bden, k)
end
private :__ir_addsub__
- # rb_rational_plus
- def +(other)
- if ::Integer === other
- ::Rational.__canonicalize_no_reduce__(@numerator + other * @denominator, @denominator)
- elsif ::Float === other
- to_f + other
- elsif ::Rational === other
- __ir_addsub__(@numerator, @denominator, other.numerator, other.denominator, :+)
- else
- __ir_num_coerce_bin__(other, :+)
- end
- end
-
- # rb_rational_minus
- def -(other)
- if ::Integer === other
- ::Rational.__canonicalize_no_reduce__(@numerator - other * @denominator, @denominator)
- elsif ::Float === other
- to_f - other
- elsif ::Rational === other
- __ir_addsub__(@numerator, @denominator, other.numerator, other.denominator, :-)
- else
- __ir_num_coerce_bin__(other, :-)
- end
- end
+ # +, -, *, /, quo, <=>, ==, #to_f and #hash are C# (RubyRational): an Integer, a Float or a
+ # Rational operand is answered there, anything else comes back to __ir_num_coerce_bin__ and
+ # __ir_num_coerce_cmp__ above.
# f_muldiv
def __ir_muldiv__(anum, aden, bnum, bden, k)
@@ -233,38 +197,6 @@ def __ir_muldiv__(anum, aden, bnum, bden, k)
end
private :__ir_muldiv__
- # rb_rational_mul
- def *(other)
- if ::Integer === other
- __ir_muldiv__(@numerator, @denominator, other, 1, :*)
- elsif ::Float === other
- to_f * other
- elsif ::Rational === other
- __ir_muldiv__(@numerator, @denominator, other.numerator, other.denominator, :*)
- else
- __ir_num_coerce_bin__(other, :*)
- end
- end
-
- # rb_rational_div
- def /(other)
- if ::Integer === other
- ::Kernel.raise(::ZeroDivisionError, "divided by 0") if other == 0
- __ir_muldiv__(@numerator, @denominator, other, 1, :/)
- elsif ::Float === other
- to_f / other
- elsif ::Rational === other
- ::Kernel.raise(::ZeroDivisionError, "divided by 0") if other.numerator == 0
- if @numerator == 1 && @denominator == 1
- return ::Rational.__canonicalize_no_reduce__(other.denominator, other.numerator)
- end
- __ir_muldiv__(@numerator, @denominator, other.numerator, other.denominator, :/)
- else
- __ir_num_coerce_bin__(other, :/)
- end
- end
- alias quo /
-
# nurat_fdiv
def fdiv(other)
return self / 0.0 if other == 0
@@ -283,12 +215,12 @@ def **(other)
other = other.numerator if ::Rational === other && other.denominator == 1
if ::Numeric === other && !(::Float === other)
- if @denominator == 1
- if @numerator == 1
+ if denominator == 1
+ if numerator == 1
return ::Rational.__new_internal__(1, 1)
- elsif @numerator == -1 && ::Integer === other
+ elsif numerator == -1 && ::Integer === other
return ::Rational.__new_internal__(other.odd? ? -1 : 1, 1)
- elsif @numerator == 0
+ elsif numerator == 0
if ::Numeric.__ir_negative_p__(other)
::Kernel.raise(::ZeroDivisionError, "divided by 0")
else
@@ -300,11 +232,11 @@ def **(other)
if ::Integer === other && other.abs <= FIXNUM_MAX__
if other > 0
- num = @numerator ** other
- den = @denominator ** other
+ num = numerator ** other
+ den = denominator ** other
elsif other < 0
- num = @denominator ** -other
- den = @numerator ** -other
+ num = denominator ** -other
+ den = numerator ** -other
else
num = 1
den = 1
@@ -333,37 +265,6 @@ def **(other)
FIXNUM_MAX__ = 2**62 - 1
private_constant :FIXNUM_MAX__
- # rb_rational_cmp
- def <=>(other)
- case other
- when ::Integer
- return @numerator <=> other if @denominator == 1
- (@numerator - other * @denominator) <=> 0
- when ::Rational
- (@numerator * other.denominator - other.numerator * @denominator) <=> 0
- when ::Float
- ::Rational.__dbl_cmp__(to_f, other)
- else
- __ir_num_coerce_cmp__(other, :<=>)
- end
- end
-
- # nurat_eqeq_p
- def ==(other)
- if ::Integer === other
- return true if @numerator == 0 && other == 0
- return false unless @denominator == 1
- @numerator == other
- elsif ::Float === other
- ::Rational.__dbl_cmp__(to_f, other) == 0
- elsif ::Rational === other
- return true if @numerator == 0 && other.numerator == 0
- @numerator == other.numerator && @denominator == other.denominator
- else
- other == self ? true : false
- end
- end
-
# nurat_coerce
def coerce(other)
case other
@@ -387,41 +288,41 @@ def coerce(other)
# nurat_positive_p / nurat_negative_p
def positive?
- @numerator > 0
+ numerator > 0
end
def negative?
- @numerator < 0
+ numerator < 0
end
# rb_rational_abs
def abs
- return ::Rational.__new_internal__(-@numerator, @denominator) if @numerator < 0
+ return ::Rational.__new_internal__(-numerator, denominator) if numerator < 0
self
end
alias magnitude abs
# nurat_floor / nurat_ceil / nurat_truncate: the precision-free forms.
def __ir_floor__
- @numerator.div(@denominator)
+ numerator.div(denominator)
end
private :__ir_floor__
def __ir_ceil__
- -((-@numerator).div(@denominator))
+ -((-numerator).div(denominator))
end
private :__ir_ceil__
def __ir_truncate__
- return -((-@numerator).div(@denominator)) if @numerator < 0
- @numerator.div(@denominator)
+ return -((-numerator).div(denominator)) if numerator < 0
+ numerator.div(denominator)
end
private :__ir_truncate__
# nurat_round_half_up / _down / _even
def __ir_round_half_up__
- num = @numerator
- den = @denominator
+ num = numerator
+ den = denominator
neg = num < 0
num = -num if neg
num = (num * 2 + den).div(den * 2)
@@ -430,8 +331,8 @@ def __ir_round_half_up__
private :__ir_round_half_up__
def __ir_round_half_down__
- num = @numerator
- den = @denominator
+ num = numerator
+ den = denominator
neg = num < 0
num = -num if neg
num = (num * 2 + den - 1).div(den * 2)
@@ -440,8 +341,8 @@ def __ir_round_half_down__
private :__ir_round_half_down__
def __ir_round_half_even__
- num = @numerator
- den = @denominator
+ num = numerator
+ den = denominator
neg = num < 0
num = -num if neg
num, rem = (num * 2 + den).divmod(den * 2)
@@ -483,7 +384,7 @@ def __ir_round_n__(n, func)
# A denominator divides a power of ten only if it is made of twos and fives.
def __ir_exact_at__(ndigits)
- d = @denominator
+ d = denominator
twos = 0
while d.even?
d >>= 1
@@ -544,12 +445,6 @@ def to_i
__ir_truncate__
end
- # nurat_to_double: Integer#fdiv divides exactly before rounding, so a quotient of two
- # numbers past Float::MAX still comes out right.
- def to_f
- @numerator.fdiv(@denominator)
- end
-
def to_r
self
end
@@ -586,8 +481,8 @@ def rationalize(*args)
end
return self if args.empty?
e = args[0].abs
- neg = @numerator < 0
- rat = neg ? ::Rational.__canonicalize__(-@numerator, @denominator) : self
+ neg = numerator < 0
+ rat = neg ? ::Rational.__canonicalize__(-numerator, denominator) : self
zero = ::Integer === e && e == 0
a = zero ? rat : rat - e
b = zero ? rat : rat + e
@@ -597,18 +492,13 @@ def rationalize(*args)
::Rational.__canonicalize__(p, q)
end
- # nurat_hash
- def hash
- [@numerator, @denominator, ::Rational].hash
- end
-
# nurat_to_s / nurat_inspect
def to_s
- "#{@numerator}/#{@denominator}"
+ "#{numerator}/#{denominator}"
end
def inspect
- "(#{@numerator.inspect}/#{@denominator.inspect})"
+ "(#{numerator.inspect}/#{denominator.inspect})"
end
# Rational and Complex are special objects in MRI's sense: #dup and #clone answer the
@@ -626,7 +516,7 @@ def clone(freeze: nil)
# nurat_marshal_dump / nurat_marshal_load (by way of Rational::compatible in MRI).
def marshal_dump
- [@numerator, @denominator]
+ [numerator, denominator]
end
private :marshal_dump
@@ -644,10 +534,7 @@ def marshal_load(a)
elsif den == 0
::Kernel.raise(::ZeroDivisionError, "divided by 0")
end
- @numerator = num
- @denominator = den
- freeze
- self
+ __ir_set_parts__(num, den)
end
private :marshal_load
@@ -996,7 +883,8 @@ def self.__ir_to_int__(value)
# nurat_f_rational. Rational(x) and Rational(x, nil) are different calls - the second
# is a TypeError - so the arity is read off *args rather than from a default.
- def Rational(*args, exception: true)
+ # Kernel#Rational is C# (KernelOps.ToRational); what it does not answer itself comes here.
+ def __ir_Rational_slow__(*args, exception: true)
# Rational(Integer, Integer) and Rational(Rational, Integer) - ActiveSupport's
# Time#change makes one of each for every Time#advance - go straight to what
# __convert__ ends with for them: __canonicalize__, and the Rational itself or its
@@ -1014,7 +902,7 @@ def Rational(*args, exception: true)
end
::Rational.__convert__(args[0], args[1], args.size == 2, ::Kernel.__opts_exception__(exception))
end
- module_function :Rational
+ module_function :__ir_Rational_slow__
end
class Integer