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