diff --git a/Src/IronRuby.Tests/RubyTests.cs b/Src/IronRuby.Tests/RubyTests.cs
index 9bbd69bb6..28b825ee7 100644
--- a/Src/IronRuby.Tests/RubyTests.cs
+++ b/Src/IronRuby.Tests/RubyTests.cs
@@ -876,6 +876,7 @@ public Tests(Driver/*!*/ driver) {
Range_MinMaxInclude1,
Integer_Power1,
Rational_CSharp1,
+ Complex_CSharp1,
Method_LightFrame1,
Method_LightFrame2,
Hash_SelfKey1,
diff --git a/Src/IronRuby.Tests/Runtime/MiscTests.cs b/Src/IronRuby.Tests/Runtime/MiscTests.cs
index 4b6c911b9..4e41ae55f 100644
--- a/Src/IronRuby.Tests/Runtime/MiscTests.cs
+++ b/Src/IronRuby.Tests/Runtime/MiscTests.cs
@@ -3171,6 +3171,63 @@ p r
");
}
+ ///
+ /// Complex as a C# type (RubyComplex) with +, -, *, /, ==, #hash and #abs in C# for Integer and Float
+ /// parts, Kernel#Complex and #to_c; Rational parts, coercion and the rest left to complex.rb; Math.hypot
+ /// rounded as libm's is. The expected output is CRuby's.
+ ///
+ public void Complex_CSharp1() {
+ TestOutput(@"
+require 'ruby4'
+def t(l); r = begin; yield; rescue => e; [e.class, e.message]; end; puts ""#{l}: #{r.inspect}""; end
+a = Complex(1, 2); b = Complex(3.5, -1); r = Complex(Rational(1, 2), 3); z = Complex(0, 0)
+t('basic') { [a, a.real, a.imaginary, a.imag, a.frozen?, a.instance_variables, a.class.superclass, Complex(1), Complex(1.5), Complex(""1+2i"")] }
+t('add') { [a + b, a + 1, a + 2.5, a + r, a - b, a - 1, a + Rational(1, 3), -a, a + Complex(0, 0)] }
+t('mul') { [a * b, a * 2, a * 0.5, a * r, a * Complex(0, 1), Complex(Float::INFINITY, 0) * 0, a * z] }
+t('div') { [a / b, a / 2, a / 0.5, a.quo(a), a / r, a.fdiv(2), Complex(1, 1) / Complex(1, -1)] }
+t('div0') { a / 0 }
+t('div0f') { a / 0.0 }
+t('pow') { [a ** 2, a ** -1, a ** 0.5, a ** a, a ** Rational(1, 2), Complex(0, 1) ** 2] }
+t('eq') { [a == Complex(1, 2), a == b, Complex(3) == 3, Complex(3, 0.0) == 3, a == ""x"", a.eql?(Complex(1, 2)), a.eql?(Complex(1.0, 2)), Complex(1, 0) <=> 2, a <=> 1] }
+t('hash') { [a.hash == Complex(1, 2).hash, {a => 1}[Complex(1, 2)], a.hash == Complex(1.0, 2).hash] }
+t('abs') { [a.abs, a.abs2, a.arg, a.polar, a.rectangular, a.conjugate, b.abs, Complex(3, 4).abs, Complex(-0.0, 0).arg] }
+t('fmt') { [a.to_s, a.inspect, b.to_s, r.inspect, Complex(1, Float::NAN).to_s, Complex(1, -0.0).inspect] }
+t('marshal') { [Marshal.load(Marshal.dump(a)), Marshal.load(Marshal.dump(r))] }
+o = Object.new; def o.coerce(x) = [x, Complex(1, 1)]
+t('coerce') { [a + o, a * o] }
+t('bad') { a + ""x"" }
+t('int ops') { [1 + a, 2 * a, 3 - a, 4 / a, 1.5 * a, Rational(1, 2) + a, 2 ** a] }
+t('misc') { [a.real?, 1.real?, a.to_c, 5.to_c, 2.5.to_c, Rational(1, 2).to_c, a.finite?, Complex(Float::INFINITY, 0).infinite?, Complex.polar(1, Math::PI / 2).real.round(10), Complex.rectangular(1, 2)] }
+t('Complex()') { [Complex(1, 2), Complex(""3""), Complex(nil, exception: false), Complex(""x"", exception: false), Complex(1, 2.0)] }
+t('cerr') { Complex(nil) }
+t('hypot') { [Math.hypot(3.5, 1), Math.hypot(3, 4), Math.hypot(1e300, 1e300), Math.hypot(1e-300, 1e-300), Math.hypot(-0.0, 0), Math.hypot(Float::INFINITY, Float::NAN), Math.hypot(5e-324, 5e-324)] }
+t('undef') { [Complex.instance_method(:==).owner, (Complex(1) < 2 rescue $!.class)] }
+t('int32 edge') { m = -2147483648; x = 2147483647; [Complex(m, m) * Complex(m, m), Complex(x, x) * Complex(x, -x), Complex(m, x) * Complex(x, m), Complex(m, 0) * m + Complex(m, 0) * m, Complex(x, x) + Complex(x, x), Complex(m, m) - Complex(x, x)] }
+", @"
+basic: [(1+2i), 1, 2, 2, true, [], Numeric, (1+0i), (1.5+0i), (1+2i)]
+add: [(4.5+1i), (2+2i), (3.5+2i), ((3/2)+5i), (-2.5+3i), (0+2i), ((4/3)+2i), (-1-2i), (1+2i)]
+mul: [(5.5+6.0i), (2+4i), (0.5+1.0i), ((-11/2)+(4/1)*i), (-2+1i), (NaN+0i), (0+0i)]
+div: [(0.11320754716981132+0.6037735849056602i), ((1/2)+1i), (2.0+4.0i), (1+0i), ((26/37)-(8/37)*i), (0.5+1.0i), (0+1i)]
+div0: [ZeroDivisionError, ""divided by 0""]
+div0f: (Infinity+Infinity*i)
+pow: [(-3+4i), ((1/5)-(2/5)*i), (1.272019649514069+0.7861513777574233i), (-0.22251715680177267+0.10070913113607541i), (1.272019649514069+0.7861513777574233i), (-1+0i)]
+eq: [true, false, true, true, false, true, false, -1, nil]
+hash: [true, 1, false]
+abs: [2.23606797749979, 5, 1.1071487177940904, [2.23606797749979, 1.1071487177940904], [1, 2], (1-2i), 3.640054944640259, 5.0, 3.141592653589793]
+fmt: [""1+2i"", ""(1+2i)"", ""3.5-1i"", ""((1/2)+3i)"", ""1+NaN*i"", ""(1-0.0i)""]
+marshal: [(1+2i), ((1/2)+3i)]
+coerce: [(2+3i), (-1+3i)]
+bad: [TypeError, ""String can't be coerced into Complex""]
+int ops: [(2+2i), (2+4i), (2-2i), ((4/5)-(8/5)*i), (1.5+3.0i), ((3/2)+2i), (0.36691394948660344+1.9660554808224875i)]
+misc: [false, true, (1+2i), (5+0i), (2.5+0i), ((1/2)+0i), true, 1, 0.0, (1+2i)]
+Complex(): [(1+2i), (3+0i), nil, nil, (1+2.0i)]
+cerr: [TypeError, ""can't convert nil into Complex""]
+hypot: [3.640054944640259, 5.0, 1.4142135623730952e+300, 1.414213562373095e-300, 0.0, Infinity, 5.0e-324]
+undef: [Complex, NoMethodError]
+int32 edge: [(0+9223372036854775808i), (9223372028264841218+0i), (0+9223372032559808513i), (9223372036854775808+0i), (4294967294+4294967294i), (-4294967295-4294967295i)]
+");
+ }
+
///
/// 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,
@@ -3211,6 +3268,7 @@ public void Rational_CSharp1() {
t('badkw') { Rational(1, exception: 1) }
t('Kernel.') { Kernel.Rational(3, 9) }
t('arity') { method(:Rational).arity }
+t('int32 edge') { [Rational(2**31 - 1, 2**31 - 2) + Rational(-2**31, 2**31 - 1), Rational(-2**31, 1) * Rational(-2**31, 1), Rational(2**31, 3) / Rational(-2**31, 7), Rational(-2**31, 2**31 - 1) <=> Rational(2**31 - 1, 2**31 - 2), Rational(2**31 - 1) + (2**31 - 1), Rational(2**31, 1) + 1, Rational(-2**31, 3) - Rational(2**31 - 1, 5), Rational(2**31 - 1, -(2**31)), (-2**31) ** -1, (-2**31) ** -2] }
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)]
@@ -3242,6 +3300,7 @@ public void Rational_CSharp1() {
badkw: [ArgumentError, ""expected true or false as exception: 1""]
Kernel.: (1/3)
arity: -1
+int32 edge: [(1/4611686011984936962), (4611686018427387904/1), (-7/3), -1, (4294967294/1), (2147483649/1), (-17179869181/15), (-2147483647/2147483648), (-1/2147483648), (1/4611686018427387904)]
class: [Rational, Integer]
");
}
diff --git a/Src/Libraries/Builtins/FloatOps.cs b/Src/Libraries/Builtins/FloatOps.cs
index 1dceec6e4..1acdd4884 100644
--- a/Src/Libraries/Builtins/FloatOps.cs
+++ b/Src/Libraries/Builtins/FloatOps.cs
@@ -23,6 +23,12 @@ namespace IronRuby.Builtins {
[RubyClass("Float", Extends = typeof(double), Inherits = typeof(Numeric)), Includes(typeof(ClrFloat), Copy = true)]
[UndefineMethod("new", IsStatic = true)]
public static class FloatOps {
+ /// numeric_to_c: self + 0i (complex.rb's Numeric#to_c, for a Float).
+ [RubyMethod("to_c")]
+ public static RubyComplex/*!*/ ToComplex(RubyContext/*!*/ context, double self) {
+ return new RubyComplex(context.GetClass(typeof(RubyComplex)), self, ScriptingRuntimeHelpers.Int32ToObject(0));
+ }
+
#region Constants
///
diff --git a/Src/Libraries/Builtins/Integer.cs b/Src/Libraries/Builtins/Integer.cs
index d7bba8ec1..df947fc35 100644
--- a/Src/Libraries/Builtins/Integer.cs
+++ b/Src/Libraries/Builtins/Integer.cs
@@ -216,6 +216,12 @@ private static BigInteger ToBigInteger(object/*!*/ self) {
#endregion
+ /// numeric_to_c: self + 0i (complex.rb's Numeric#to_c, for the commonest receivers).
+ [RubyMethod("to_c")]
+ public static RubyComplex/*!*/ ToComplex(RubyContext/*!*/ context, object/*!*/ self) {
+ return new RubyComplex(context.GetClass(typeof(RubyComplex)), self, ScriptingRuntimeHelpers.Int32ToObject(0));
+ }
+
#region numerator/ord, denominator
[RubyMethod("numerator")]
diff --git a/Src/Libraries/Builtins/KernelOps.cs b/Src/Libraries/Builtins/KernelOps.cs
index 98eccf4bf..9aacebdc2 100644
--- a/Src/Libraries/Builtins/KernelOps.cs
+++ b/Src/Libraries/Builtins/KernelOps.cs
@@ -679,6 +679,56 @@ public static object ToRational(object self) {
throw ConversionArity(0, "1..2");
}
+ // Kernel#Complex(x[, y], exception: true) (MRI's nucomp_f_complex): Integers and Floats make
+ // their Complex here; anything else - Strings, Rationals, a Complex, objects, exception: - is
+ // complex.rb's __ir_Complex_slow__, whose `*args, exception:' cost ~1.7 us a call.
+
+ private static bool IsIntegerOrFloat(object value) {
+ return value is int || value is long || value is double;
+ }
+
+ [RubyMethod("Complex", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Complex", RubyMethodAttributes.PublicSingleton)]
+ public static object ToComplex(CallSiteStorage>/*!*/ slow, RubyContext/*!*/ context,
+ object self, object value) {
+ if (IsIntegerOrFloat(value)) {
+ return new RubyComplex(context.GetClass(typeof(RubyComplex)), value, ScriptingRuntimeHelpers.Int32ToObject(0));
+ }
+ var site = slow.GetCallSite("__ir_Complex_slow__", 1);
+ return site.Target(site, self, value);
+ }
+
+ [RubyMethod("Complex", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Complex", RubyMethodAttributes.PublicSingleton)]
+ public static object ToComplex(CallSiteStorage>/*!*/ slow, RubyContext/*!*/ context,
+ object self, object real, object imaginary) {
+ if (IsIntegerOrFloat(real) && IsIntegerOrFloat(imaginary)) {
+ return new RubyComplex(context.GetClass(typeof(RubyComplex)), real, imaginary);
+ }
+ var site = slow.GetCallSite("__ir_Complex_slow__", 2);
+ return site.Target(site, self, real, imaginary);
+ }
+
+ [RubyMethod("Complex", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Complex", RubyMethodAttributes.PublicSingleton)]
+ public static object ToComplex(CallSiteStorage>/*!*/ slow,
+ object self, object real, object imaginary, object options) {
+ var site = slow.GetCallSite("__ir_Complex_slow__", 3);
+ return site.Target(site, self, real, imaginary, options);
+ }
+
+ [RubyMethod("Complex", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Complex", RubyMethodAttributes.PublicSingleton)]
+ public static object ToComplex(object self, object a, object b, object c, object d, params object[]/*!*/ more) {
+ throw ConversionArity(4 + more.Length, "1..2");
+ }
+
+ [RubyMethod("Complex", RubyMethodAttributes.PrivateInstance)]
+ [RubyMethod("Complex", RubyMethodAttributes.PublicSingleton)]
+ public static object ToComplex(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;
diff --git a/Src/Libraries/Builtins/RubyComplex.cs b/Src/Libraries/Builtins/RubyComplex.cs
new file mode 100644
index 000000000..48c5749e6
--- /dev/null
+++ b/Src/Libraries/Builtins/RubyComplex.cs
@@ -0,0 +1,346 @@
+/* ****************************************************************************
+ *
+ * Complex: its representation. The methods are complex.rb's (Src/StdLib/ironruby/
+ * ironruby/complex.rb, a port of MRI 4.0's complex.c), apart from the ones moved here;
+ * what is here is what that file builds on - the real and imaginary parts, read and
+ * made without a Ruby method call or an instance variable lookup.
+ *
+ * ***************************************************************************/
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using IronRuby.Runtime;
+using IronRuby.Runtime.Calls;
+using Microsoft.Scripting.Runtime;
+
+namespace IronRuby.Builtins {
+
+ ///
+ /// A Complex: a real and an imaginary part, each a real Numeric, frozen from birth (MRI's struct
+ /// RComplex). They were @real and @image of a plain Numeric.
+ ///
+ [RubyClass("Complex", Inherits = typeof(Numeric))]
+ public class RubyComplex : Numeric {
+ // null until the parts are set: an instance Marshal allocates before #marshal_load runs
+ private object _real;
+ private object _imaginary;
+
+ public RubyComplex(RubyClass/*!*/ cls)
+ : base(cls) {
+ }
+
+ public RubyComplex(RubyClass/*!*/ cls, object real, object imaginary)
+ : base(cls) {
+ _real = real;
+ _imaginary = imaginary;
+ Freeze();
+ }
+
+ public object Real { get { return _real; } }
+ public object Imaginary { get { return _imaginary; } }
+
+ [RubyMethod("real")]
+ public static object GetReal(RubyComplex/*!*/ self) {
+ return self._real;
+ }
+
+ [RubyMethod("imaginary"), RubyMethod("imag")]
+ public static object GetImaginary(RubyComplex/*!*/ self) {
+ return self._imaginary;
+ }
+
+ /// MRI's nucomp_s_new_internal: no canonicalisation, no checks; frozen.
+ [RubyMethod("__raw__", RubyMethodAttributes.PublicSingleton)]
+ public static RubyComplex/*!*/ Raw(RubyClass/*!*/ self, object real, object imaginary) {
+ return new RubyComplex(self, real, imaginary);
+ }
+
+ #region arithmetic on Integer and Float parts (complex.c's f_add, f_sub, f_mul, safe_mul, comp_mul)
+
+ // The fast paths take parts and operands that are an Int32-sized Integer or a Float; those
+ // are combined in a long (an Integer stays one) or a double. Anything else - a Rational part,
+ // a Bignum, another kind of operand - is complex.rb's (the __ir_*_slow__ methods), which
+ // these follow step for step: complex.c's helpers skip an exact 0 or 1 operand, and that is
+ // what keeps an exact zero from turning 0.0 into 0, which ruby/spec observes.
+
+ private static bool IsSimple(object x) {
+ if (x is double) { return true; }
+ long v;
+ return TryLong(x, out v) && v >= Int32.MinValue && v <= Int32.MaxValue;
+ }
+
+ private static bool TryLong(object x, out long v) {
+ if (x is int) { v = (int)x; return true; }
+ if (x is long) { v = (long)x; return true; }
+ v = 0;
+ return false;
+ }
+
+ // an exact Integer 0 (FIXNUM_ZERO_P)
+ private static bool FixZero(object x) {
+ long v;
+ return TryLong(x, out v) && v == 0;
+ }
+
+ private static bool FixOne(object x) {
+ long v;
+ return TryLong(x, out v) && v == 1;
+ }
+
+ // f_zero_p: 0.0 counts
+ private static bool Zero(object x) {
+ if (x is double) { return (double)x == 0.0; }
+ return FixZero(x);
+ }
+
+ private static double D(object x) {
+ if (x is double) { return (double)x; }
+ long v;
+ TryLong(x, out v);
+ return v;
+ }
+
+ // The operands can be products of two Int32-sized parts (comp_mul), so a sum or difference of
+ // two of them can pass a long: Complex(-2**31, -2**31) ** 2 has 2**63 for its imaginary part.
+ private static object Plus(object x, object y) {
+ long a, b;
+ if (TryLong(x, out a) && TryLong(y, out b)) {
+ long r = unchecked(a + b);
+ if (((a ^ r) & (b ^ r)) < 0) { return Protocols.Normalize((BigInteger)a + b); }
+ return Protocols.Normalize(r);
+ }
+ return D(x) + D(y);
+ }
+
+ private static object Minus(object x, object y) {
+ long a, b;
+ if (TryLong(x, out a) && TryLong(y, out b)) {
+ long r = unchecked(a - b);
+ if (((a ^ b) & (a ^ r)) < 0) { return Protocols.Normalize((BigInteger)a - b); }
+ return Protocols.Normalize(r);
+ }
+ return D(x) - D(y);
+ }
+
+ private static object Times(object x, object y) {
+ long a, b;
+ if (TryLong(x, out a) && TryLong(y, out b)) { return Protocols.Normalize(a * b); }
+ return D(x) * D(y);
+ }
+
+ private static object FAdd(object x, object y) {
+ if (!(x is double)) {
+ if (FixZero(x)) { return y; }
+ if (FixZero(y)) { return x; }
+ } else if (FixZero(y)) {
+ return x;
+ }
+ return Plus(x, y);
+ }
+
+ private static object FSub(object x, object y) {
+ return FixZero(y) ? x : Minus(x, y);
+ }
+
+ private static object FMul(object x, object y) {
+ if (!(x is double)) {
+ if (FixZero(y)) { return ScriptingRuntimeHelpers.Int32ToObject(0); }
+ if (FixZero(x) && !(y is double)) { return ScriptingRuntimeHelpers.Int32ToObject(0); }
+ if (FixOne(x)) { return y; }
+ if (FixOne(y)) { return x; }
+ } else if (FixOne(y)) {
+ return x;
+ }
+ return Times(x, y);
+ }
+
+ private static bool SignBit(double x) {
+ return !Double.IsNaN(x) && (x < 0.0 || (x == 0.0 && Double.IsNegative(x)));
+ }
+
+ // safe_mul: a Float multiplied by a zero is reduced to its sign first
+ private static object SafeMul(object a, object b, bool az, bool bz) {
+ if (!az && bz && a is double && !Double.IsNaN((double)a)) {
+ a = SignBit((double)a) ? -1.0 : 1.0;
+ }
+ if (!bz && az && b is double && !Double.IsNaN((double)b)) {
+ b = SignBit((double)b) ? -1.0 : 1.0;
+ }
+ return FMul(a, b);
+ }
+
+ private RubyComplex/*!*/ Make(object real, object imaginary) {
+ return new RubyComplex(ImmediateClass.GetNonSingletonClass(), real, imaginary);
+ }
+
+ private bool HasSimpleParts {
+ get { return IsSimple(_real) && IsSimple(_imaginary); }
+ }
+
+ private static object Slow(CallSiteStorage>/*!*/ slow, string/*!*/ name, RubyComplex/*!*/ self, object other) {
+ var site = slow.GetCallSite(name, 1);
+ return site.Target(site, self, other);
+ }
+
+ [RubyMethod("+")]
+ public static object Add(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self, object other) {
+ if (self.HasSimpleParts) {
+ var c = other as RubyComplex;
+ if (c != null) {
+ if (c.HasSimpleParts) {
+ return self.Make(FAdd(self._real, c._real), FAdd(self._imaginary, c._imaginary));
+ }
+ } else if (IsSimple(other)) {
+ return self.Make(FAdd(self._real, other), self._imaginary);
+ }
+ }
+ return Slow(slow, "__ir_add_slow__", self, other);
+ }
+
+ [RubyMethod("-")]
+ public static object Subtract(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self, object other) {
+ if (self.HasSimpleParts) {
+ var c = other as RubyComplex;
+ if (c != null) {
+ if (c.HasSimpleParts) {
+ return self.Make(FSub(self._real, c._real), FSub(self._imaginary, c._imaginary));
+ }
+ } else if (IsSimple(other)) {
+ return self.Make(FSub(self._real, other), self._imaginary);
+ }
+ }
+ return Slow(slow, "__ir_sub_slow__", self, other);
+ }
+
+ [RubyMethod("*")]
+ public static object Multiply(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self, object other) {
+ if (self.HasSimpleParts) {
+ var c = other as RubyComplex;
+ if (c != null) {
+ if (c.HasSimpleParts) {
+ // comp_mul
+ object ar = self._real, ai = self._imaginary, br = c._real, bi = c._imaginary;
+ bool arz = Zero(ar), aiz = Zero(ai), brz = Zero(br), biz = Zero(bi);
+ object real = FSub(SafeMul(ar, br, arz, brz), SafeMul(ai, bi, aiz, biz));
+ object imag = FAdd(SafeMul(ar, bi, arz, biz), SafeMul(ai, br, aiz, brz));
+ return self.Make(real, imag);
+ }
+ } else if (IsSimple(other)) {
+ return self.Make(FMul(self._real, other), FMul(self._imaginary, other));
+ }
+ }
+ return Slow(slow, "__ir_mul_slow__", self, other);
+ }
+
+ // f_divide with f_quo, for Float parts throughout: the quotients are Floats, as they are
+ // there (Integer parts make Rationals, which complex.rb makes).
+ [RubyMethod("/"), RubyMethod("quo")]
+ public static object Divide(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self, object other) {
+ if (self._real is double && self._imaginary is double) {
+ double ar = (double)self._real, ai = (double)self._imaginary;
+ var c = other as RubyComplex;
+ if (c != null) {
+ if (c._real is double && c._imaginary is double) {
+ double br = (double)c._real, bi = (double)c._imaginary;
+ double x, y;
+ if (Math.Abs(br) > Math.Abs(bi)) {
+ double r = bi / br;
+ double n = br * (1 + r * r);
+ x = (ar + ai * r) / n;
+ y = (ai - ar * r) / n;
+ } else {
+ double r = br / bi;
+ double n = bi * (1 + r * r);
+ x = (ar * r + ai) / n;
+ y = (ai * r - ar) / n;
+ }
+ return self.Make(x, y);
+ }
+ } else if (other is double) {
+ return self.Make(ar / (double)other, ai / (double)other);
+ }
+ }
+ return Slow(slow, "__ir_div_slow__", self, other);
+ }
+
+ // nucomp_eqeq_p, with f_eqeq_p: a Float on either side compares as Floats
+ private static bool PartEqual(object x, object y) {
+ long a, b;
+ if (TryLong(x, out a) && TryLong(y, out b)) { return a == b; }
+ return D(x) == D(y);
+ }
+
+ [RubyMethod("==")]
+ public static object Equal(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self, object other) {
+ if (self.HasSimpleParts) {
+ var c = other as RubyComplex;
+ if (c != null) {
+ if (c.HasSimpleParts) {
+ return ScriptingRuntimeHelpers.BooleanToObject(PartEqual(self._real, c._real) && PartEqual(self._imaginary, c._imaginary));
+ }
+ } else if (IsSimple(other)) {
+ return ScriptingRuntimeHelpers.BooleanToObject(PartEqual(self._real, other) && Zero(self._imaginary));
+ }
+ }
+ return Slow(slow, "__ir_eqeq_slow__", self, other);
+ }
+
+ // nucomp_hash: a function of the parts and their classes (#eql? tells 1 from 1.0)
+ [RubyMethod("hash")]
+ public static object Hash(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self) {
+ if (self.HasSimpleParts) {
+ return ScriptingRuntimeHelpers.Int32ToObject(unchecked((PartHash(self._real) * 31 + PartHash(self._imaginary)) * 31 + 0x436f6d70));
+ }
+ var site = slow.GetCallSite("__ir_hash_slow__", 0);
+ return site.Target(site, self);
+ }
+
+ private static int PartHash(object x) {
+ if (x is double) { return ((double)x).GetHashCode() ^ 0x5a5a5a5a; }
+ long v;
+ TryLong(x, out v);
+ return v.GetHashCode();
+ }
+
+ // rb_complex_abs
+ [RubyMethod("abs"), RubyMethod("magnitude")]
+ public static object Abs(CallSiteStorage>/*!*/ slow, RubyComplex/*!*/ self) {
+ if (self.HasSimpleParts) {
+ object re = self._real, im = self._imaginary;
+ if (Zero(re)) {
+ object a = AbsPart(im);
+ return (re is double && !(im is double)) ? (object)D(a) : a;
+ }
+ if (Zero(im)) {
+ object a = AbsPart(re);
+ return (!(re is double) && im is double) ? (object)D(a) : a;
+ }
+ return RubyMath.HypotDouble(D(re), D(im));
+ }
+ var site = slow.GetCallSite("__ir_abs_slow__", 0);
+ return site.Target(site, self);
+ }
+
+ private static object AbsPart(object x) {
+ if (x is double) { return Math.Abs((double)x); }
+ long v;
+ TryLong(x, out v);
+ return Protocols.Normalize(Math.Abs(v));
+ }
+
+ #endregion
+
+ /// The parts of the instance Marshal allocates for #marshal_load, which is then frozen.
+ [RubyMethod("__ir_set_parts__", RubyMethodAttributes.PrivateInstance)]
+ public static RubyComplex/*!*/ SetParts(RubyContext/*!*/ context, RubyComplex/*!*/ self, object real, object imaginary) {
+ if (self.IsFrozen) {
+ throw RubyExceptions.CreateObjectFrozenError(context, self);
+ }
+ self._real = real;
+ self._imaginary = imaginary;
+ self.Freeze();
+ return self;
+ }
+ }
+}
diff --git a/Src/Libraries/Builtins/RubyMath.cs b/Src/Libraries/Builtins/RubyMath.cs
index 2577ee9fc..4fa31b466 100644
--- a/Src/Libraries/Builtins/RubyMath.cs
+++ b/Src/Libraries/Builtins/RubyMath.cs
@@ -277,6 +277,15 @@ public static object Hypot(CallSiteStorage
+ /// C's hypot(): x*x + y*y with one rounding (a fused multiply-add) while neither square can
+ /// overflow or underflow - the scaled form below rounds twice more, and came out a unit in
+ /// the last place off libm's for (3.5).hypot(1) - and scaled by the larger one otherwise.
+ ///
+ internal static double HypotDouble(double dx, double dy) {
dx = SM.Abs(dx);
dy = SM.Abs(dy);
// an infinity wins over a NaN, which is what C's hypot() promises
@@ -284,9 +293,31 @@ public static object Hypot(CallSiteStorage 1e150) {
+ scale = 4.149515568880993e+180; // 2**600
+ dx *= 2.409919865102884e-181; // 2**-600
+ dy *= 2.409919865102884e-181;
+ } else if (dy < 1e-150) {
+ scale = 2.409919865102884e-181;
+ dx *= 4.149515568880993e+180;
+ dy *= 4.149515568880993e+180;
+ }
+ // x*x + y*y to about twice double precision: each square exactly as a sum of two
+ // doubles (the low half by a fused multiply-add), the four added without losing the
+ // small ones; then one Newton step on the square root, whose own square is again
+ // taken exactly. That is within a hair of the correctly rounded result libm gives.
+ double sx = dx * dx, ex = SM.FusedMultiplyAdd(dx, dx, -sx);
+ double sy = dy * dy, ey = SM.FusedMultiplyAdd(dy, dy, -sy);
+ double sum = sx + sy;
+ double err = ((sx - sum) + sy) + (ex + ey);
+ double h = SM.Sqrt(sum + err);
+ double hh = h * h, he = SM.FusedMultiplyAdd(h, h, -hh);
+ return (h + (((sum - hh) - he) + err) / (2.0 * h)) * scale;
}
[RubyMethodAttribute("frexp", RubyMethodAttributes.PrivateInstance)]
diff --git a/Src/Libraries/Initializers.Generated.cs b/Src/Libraries/Initializers.Generated.cs
index 87702146d..9b7fba10f 100644
--- a/Src/Libraries/Initializers.Generated.cs
+++ b/Src/Libraries/Initializers.Generated.cs
@@ -223,6 +223,7 @@ protected override void LoadModules() {
Context.TrueClass = DefineGlobalClass("TrueClass", typeof(IronRuby.Builtins.TrueClass), 0x0000000F, Context.ObjectClass, LoadTrueClass_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray);
DefineGlobalClass("UnboundMethod", typeof(IronRuby.Builtins.UnboundMethod), 0x0000000F, Context.ObjectClass, LoadUnboundMethod_Instance, null, null, IronRuby.Builtins.RubyModule.EmptyArray);
// Skipped primitive: Class
+ DefineGlobalClass("Complex", typeof(IronRuby.Builtins.RubyComplex), 0x0000000F, def70, LoadComplex_Instance, LoadComplex_Class, null, IronRuby.Builtins.RubyModule.EmptyArray);
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)
);
@@ -822,6 +823,82 @@ private static void LoadComparable_Instance(IronRuby.Builtins.RubyModule/*!*/ mo
}
+ private static void LoadComplex_Instance(IronRuby.Builtins.RubyModule/*!*/ module) {
+ DefineLibraryMethod(module, "__ir_set_parts__", 0x52,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyComplex.SetParts)
+ );
+
+ DefineLibraryMethod(module, "-", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object, System.Object>(IronRuby.Builtins.RubyComplex.Subtract)
+ );
+
+ DefineLibraryMethod(module, "*", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object, System.Object>(IronRuby.Builtins.RubyComplex.Multiply)
+ );
+
+ DefineLibraryMethod(module, "/", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object, System.Object>(IronRuby.Builtins.RubyComplex.Divide)
+ );
+
+ DefineLibraryMethod(module, "+", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object, System.Object>(IronRuby.Builtins.RubyComplex.Add)
+ );
+
+ DefineLibraryMethod(module, "==", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object, System.Object>(IronRuby.Builtins.RubyComplex.Equal)
+ );
+
+ DefineLibraryMethod(module, "abs", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object>(IronRuby.Builtins.RubyComplex.Abs)
+ );
+
+ DefineLibraryMethod(module, "hash", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object>(IronRuby.Builtins.RubyComplex.Hash)
+ );
+
+ DefineLibraryMethod(module, "imag", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyComplex.GetImaginary)
+ );
+
+ DefineLibraryMethod(module, "imaginary", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyComplex.GetImaginary)
+ );
+
+ DefineLibraryMethod(module, "magnitude", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object>(IronRuby.Builtins.RubyComplex.Abs)
+ );
+
+ DefineLibraryMethod(module, "quo", 0x51,
+ 0x00000000U,
+ new Func>, IronRuby.Builtins.RubyComplex, System.Object, System.Object>(IronRuby.Builtins.RubyComplex.Divide)
+ );
+
+ DefineLibraryMethod(module, "real", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyComplex.GetReal)
+ );
+
+ }
+
+ private static void LoadComplex_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
+ DefineLibraryMethod(module, "__raw__", 0x61,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.RubyComplex.Raw)
+ );
+
+ }
+
private static void LoadDir_Instance(IronRuby.Builtins.RubyModule/*!*/ module) {
DefineLibraryMethod(module, "close", 0x51,
0x00000000U,
@@ -2934,6 +3011,11 @@ private static void LoadFloat_Instance(IronRuby.Builtins.RubyModule/*!*/ module)
new Func(IronRuby.Builtins.FloatOps.Eql)
);
+ DefineLibraryMethod(module, "to_c", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.FloatOps.ToComplex)
+ );
+
}
private static void LoadFloat_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
@@ -3256,6 +3338,11 @@ private static void LoadInteger_Instance(IronRuby.Builtins.RubyModule/*!*/ modul
new Func(IronRuby.Builtins.Integer.Times)
);
+ DefineLibraryMethod(module, "to_c", 0x51,
+ 0x00000000U,
+ new Func(IronRuby.Builtins.Integer.ToComplex)
+ );
+
DefineLibraryMethod(module, "to_i", 0x51,
0x00000000U,
new Func(IronRuby.Builtins.Integer.ToInteger)
@@ -4887,6 +4974,15 @@ private static void LoadKernel_Instance(IronRuby.Builtins.RubyModule/*!*/ module
new Func(IronRuby.Builtins.KernelOps.GetClrMember)
);
+ DefineLibraryMethod(module, "Complex", 0x52,
+ new[] { 0x00000000U, 0x00000000U, 0x00000000U, 0x80000000U, 0x00000000U},
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func>, System.Object, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func(IronRuby.Builtins.KernelOps.ToComplex)
+ );
+
DefineLibraryMethod(module, "define_singleton_method", 0x51,
new[] { 0x0002000cU, 0x0000000cU, 0x0002000cU, 0x0000000cU, 0x0004000aU, 0x0000000aU, 0x0002000cU, 0x0000000cU},
new Func(IronRuby.Builtins.KernelOps.DefineSingletonMethod),
@@ -5443,6 +5539,15 @@ private static void LoadKernel_Class(IronRuby.Builtins.RubyModule/*!*/ module) {
new Func(IronRuby.Builtins.KernelOps.Catch)
);
+ DefineLibraryMethod(module, "Complex", 0x61,
+ new[] { 0x00000000U, 0x00000000U, 0x00000000U, 0x80000000U, 0x00000000U},
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func>, IronRuby.Runtime.RubyContext, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func>, System.Object, System.Object, System.Object, System.Object, System.Object>(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func(IronRuby.Builtins.KernelOps.ToComplex),
+ new Func(IronRuby.Builtins.KernelOps.ToComplex)
+ );
+
DefineLibraryMethod(module, "eval", 0x61,
0x00020014U, 0x0002001cU,
new Func(IronRuby.Builtins.KernelOps.Evaluate),
diff --git a/Src/StdLib/ironruby/ironruby/complex.rb b/Src/StdLib/ironruby/ironruby/complex.rb
index d60549ae9..f80c42901 100644
--- a/Src/StdLib/ironruby/ironruby/complex.rb
+++ b/Src/StdLib/ironruby/ironruby/complex.rb
@@ -2,7 +2,7 @@
# on Numeric, Float and String, and Kernel#Complex. Loaded by ruby4.rb as part of the
# core prelude, after rational.rb.
#
-# A Complex is a real part in @real and an imaginary part in @image (MRI's names, which
+# A Complex is a real part in self.real and an imaginary part in self.imaginary (MRI's names, which
# its marshal format carries), frozen from birth. complex.c undefines Complex.new and
# Complex.allocate; instances are made by __raw__ (nucomp_s_new_internal).
#
@@ -13,23 +13,13 @@
class Complex < Numeric
class << self
- alias_method :__ir_allocate__, :allocate
- private :__ir_allocate__
undef_method :allocate
undef_method :new
end
- # nucomp_s_new_internal: no canonicalisation, no checks; OBJ_FREEZE.
- def self.__raw__(real, imag)
- __ir_allocate__.__send__(:__ir_set_parts__, real, imag)
- end
-
- def __ir_set_parts__(real, imag)
- @real = real
- @image = imag
- freeze
- end
- private :__ir_set_parts__
+ # A Complex's parts are fields of the C# RubyComplex (Src/Libraries/Builtins/RubyComplex.cs),
+ # read by #real and #imaginary; __raw__ (nucomp_s_new_internal: no canonicalisation, no
+ # checks, frozen) and the __ir_set_parts__ #marshal_load uses are C# too.
# --- complex.c's arithmetic helpers ----------------------------------------
@@ -315,44 +305,43 @@ def self.__sinpi__(w)
# `Complex(1) < 2` is a NoMethodError, not a comparison of magnitudes. The undefinition
# is an entry in Complex's own table, so it also hides what ruby4.rb adds to Numeric
# later; a name nothing defines yet is given a definition here to undefine.
- ((::Comparable.instance_methods(false) + ::Comparable.private_instance_methods(false)) +
+ # Comparable's #== is the exception: Complex has its own, which is C# (RubyComplex) and so
+ # already in the table.
+ ((::Comparable.instance_methods(false) + ::Comparable.private_instance_methods(false) - [:==]) +
%i[% div divmod floor ceil modulo remainder round step truncate i positive? negative?]).each do |m|
define_method(m) { |*| } unless method_defined?(m) || private_method_defined?(m)
undef_method(m)
end
# --- instance methods --------------------------------------------------------
+ #
+ # +, -, *, /, quo, ==, #hash and #abs are C# (RubyComplex) for Integer and Float parts and
+ # operands; the __ir_*_slow__ methods below are the rest of each, which C# calls.
- attr_reader :real
-
- def imaginary
- @image
- end
- alias imag imaginary
# rb_complex_uminus
def -@
- ::Complex.__new2__(-@real, -@image)
+ ::Complex.__new2__(-self.real, -self.imaginary)
end
# rb_complex_plus
- def +(other)
+ def __ir_add_slow__(other)
if ::Complex === other
- return ::Complex.__new2__(::Complex.__f_add__(@real, other.real), ::Complex.__f_add__(@image, other.imaginary))
+ return ::Complex.__new2__(::Complex.__f_add__(self.real, other.real), ::Complex.__f_add__(self.imaginary, other.imaginary))
end
if ::Numeric === other && ::Complex.__real_p__(other)
- return ::Complex.__new2__(::Complex.__f_add__(@real, other), @image)
+ return ::Complex.__new2__(::Complex.__f_add__(self.real, other), self.imaginary)
end
__ir_num_coerce_bin__(other, :+)
end
# rb_complex_minus
- def -(other)
+ def __ir_sub_slow__(other)
if ::Complex === other
- return ::Complex.__new2__(::Complex.__f_sub__(@real, other.real), ::Complex.__f_sub__(@image, other.imaginary))
+ return ::Complex.__new2__(::Complex.__f_sub__(self.real, other.real), ::Complex.__f_sub__(self.imaginary, other.imaginary))
end
if ::Numeric === other && ::Complex.__real_p__(other)
- return ::Complex.__new2__(::Complex.__f_sub__(@real, other), @image)
+ return ::Complex.__new2__(::Complex.__f_sub__(self.real, other), self.imaginary)
end
__ir_num_coerce_bin__(other, :-)
end
@@ -383,13 +372,13 @@ def self.__comp_mul__(areal, aimag, breal, bimag)
end
# rb_complex_mul
- def *(other)
+ def __ir_mul_slow__(other)
if ::Complex === other
- real, imag = ::Complex.__comp_mul__(@real, @image, other.real, other.imaginary)
+ real, imag = ::Complex.__comp_mul__(self.real, self.imaginary, other.real, other.imaginary)
return ::Complex.__new2__(real, imag)
end
if ::Numeric === other && ::Complex.__real_p__(other)
- return ::Complex.__new2__(::Complex.__f_mul__(@real, other), ::Complex.__f_mul__(@image, other))
+ return ::Complex.__new2__(::Complex.__f_mul__(self.real, other), ::Complex.__f_mul__(self.imaginary, other))
end
__ir_num_coerce_bin__(other, :*)
end
@@ -398,8 +387,8 @@ def *(other)
def __ir_divide__(other, func, id)
c = ::Complex
if ::Complex === other
- ar = @real
- ai = @image
+ ar = self.real
+ ai = self.imaginary
br = other.real
bi = other.imaginary
flo = ::Float === ar || ::Float === ai || ::Float === br || ::Float === bi
@@ -421,8 +410,8 @@ def __ir_divide__(other, func, id)
return c.__new2__(x, y)
end
if ::Numeric === other && c.__real_p__(other)
- x = ::Rational.__canonicalize_value__(c.__send__(func, @real, other))
- y = ::Rational.__canonicalize_value__(c.__send__(func, @image, other))
+ x = ::Rational.__canonicalize_value__(c.__send__(func, self.real, other))
+ y = ::Rational.__canonicalize_value__(c.__send__(func, self.imaginary, other))
return c.__new2__(x, y)
end
__ir_num_coerce_bin__(other, id)
@@ -430,10 +419,9 @@ def __ir_divide__(other, func, id)
private :__ir_divide__
# rb_complex_div
- def /(other)
+ def __ir_div_slow__(other)
__ir_divide__(other, :__f_quo__, :quo)
end
- alias quo /
# nucomp_fdiv
def fdiv(other)
@@ -456,17 +444,17 @@ def __ir_pow_special_angle__(other)
return nil unless ::Integer === other
c = ::Complex
x = nil
- if c.__zero__(@image)
- x = @real
+ if c.__zero__(self.imaginary)
+ x = self.real
dir = 0
- elsif c.__zero__(@real)
- x = @image
+ elsif c.__zero__(self.real)
+ x = self.imaginary
dir = 2
- elsif c.__eqeq__(@real, @image)
- x = @real
+ elsif c.__eqeq__(self.real, self.imaginary)
+ x = self.real
dir = 1
- elsif c.__eqeq__(@real, c.__f_negate__(@image))
- x = @image
+ elsif c.__eqeq__(self.real, c.__f_negate__(self.imaginary))
+ x = self.imaginary
dir = 3
else
dir = 0
@@ -506,7 +494,7 @@ def **(other)
other = other.real
end
if ::Integer === other && other == 1
- return c.__raw__(@real, @image)
+ return c.__raw__(self.real, self.imaginary)
end
result = __ir_pow_special_angle__(other)
@@ -565,13 +553,13 @@ def **(other)
end
# nucomp_eqeq_p
- def ==(other)
+ def __ir_eqeq_slow__(other)
c = ::Complex
if ::Complex === other
- return c.__eqeq__(@real, other.real) && c.__eqeq__(@image, other.imaginary)
+ return c.__eqeq__(self.real, other.real) && c.__eqeq__(self.imaginary, other.imaginary)
end
if ::Numeric === other && c.__real_p__(other)
- return c.__eqeq__(@real, other) && c.__zero__(@image)
+ return c.__eqeq__(self.real, other) && c.__zero__(self.imaginary)
end
c.__eqeq__(other, self)
end
@@ -580,13 +568,13 @@ def ==(other)
def <=>(other)
c = ::Complex
return __ir_num_coerce_cmp__(other, :<=>) unless ::Numeric === other
- return nil unless c.__zero__(@image)
+ return nil unless c.__zero__(self.imaginary)
if ::Complex === other
- return @real <=> other.real if c.__zero__(other.imaginary)
+ return self.real <=> other.real if c.__zero__(other.imaginary)
elsif c.__real_p__(other)
- return @real <=> other
+ return self.real <=> other
else
- return @real.__send__(:__ir_num_coerce_cmp__, other, :<=>)
+ return self.real.__send__(:__ir_num_coerce_cmp__, other, :<=>)
end
nil
end
@@ -601,37 +589,36 @@ def coerce(other)
end
# rb_complex_abs
- def abs
+ def __ir_abs_slow__
c = ::Complex
- if c.__zero__(@real)
- a = @image.abs
- a = a.to_f if ::Float === @real && !(::Float === @image)
+ if c.__zero__(self.real)
+ a = self.imaginary.abs
+ a = a.to_f if ::Float === self.real && !(::Float === self.imaginary)
return a
end
- if c.__zero__(@image)
- a = @real.abs
- a = a.to_f if !(::Float === @real) && ::Float === @image
+ if c.__zero__(self.imaginary)
+ a = self.real.abs
+ a = a.to_f if !(::Float === self.real) && ::Float === self.imaginary
return a
end
- ::Math.hypot(@real, @image)
+ ::Math.hypot(self.real, self.imaginary)
end
- alias magnitude abs
# nucomp_abs2
def abs2
c = ::Complex
- c.__f_add__(c.__f_mul__(@real, @real), c.__f_mul__(@image, @image))
+ c.__f_add__(c.__f_mul__(self.real, self.real), c.__f_mul__(self.imaginary, self.imaginary))
end
# rb_complex_arg
def arg
- ::Math.atan2(@image, @real)
+ ::Math.atan2(self.imaginary, self.real)
end
alias angle arg
alias phase arg
def rectangular
- [@real, @image]
+ [self.real, self.imaginary]
end
alias rect rectangular
@@ -641,7 +628,7 @@ def polar
# rb_complex_conjugate
def conjugate
- ::Complex.__new2__(@real, -@image)
+ ::Complex.__new2__(self.real, -self.imaginary)
end
alias conj conjugate
@@ -651,34 +638,34 @@ def real?
# nucomp_denominator / nucomp_numerator
def denominator
- ::Complex.__denominator__(@real).lcm(::Complex.__denominator__(@image))
+ ::Complex.__denominator__(self.real).lcm(::Complex.__denominator__(self.imaginary))
end
def numerator
c = ::Complex
cd = denominator
- c.__new2__(c.__f_mul__(c.__numerator__(@real), c.__f_div__(cd, c.__denominator__(@real))),
- c.__f_mul__(c.__numerator__(@image), c.__f_div__(cd, c.__denominator__(@image))))
+ c.__new2__(c.__f_mul__(c.__numerator__(self.real), c.__f_div__(cd, c.__denominator__(self.real))),
+ c.__f_mul__(c.__numerator__(self.imaginary), c.__f_div__(cd, c.__denominator__(self.imaginary))))
end
# nucomp_hash
- def hash
- [@real, @image, ::Complex].hash
+ def __ir_hash_slow__
+ [self.real, self.imaginary, ::Complex].hash
end
# nucomp_eql_p
def eql?(other)
return false unless ::Complex === other
- @real.class == other.real.class && @image.class == other.imaginary.class && self == other
+ self.real.class == other.real.class && self.imaginary.class == other.imaginary.class && self == other
end
# f_format: the imaginary part's sign, its magnitude, then "i" - with a "*" in front
# when the magnitude does not end in a digit, as in "(3/4)*i" or "Infinity*i".
def __ir_format__(s, inspecting)
- impos = !::Complex.__signbit__(@image)
- s << (inspecting ? @real.inspect : ::Kernel.String(@real))
+ impos = !::Complex.__signbit__(self.imaginary)
+ s << (inspecting ? self.real.inspect : ::Kernel.String(self.real))
s << (impos ? "+" : "-")
- mag = @image.abs
+ mag = self.imaginary.abs
s << (inspecting ? mag.inspect : ::Kernel.String(mag))
s << "*" unless s.end_with?("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
s << "i"
@@ -695,49 +682,49 @@ def inspect
# rb_complex_finite_p / rb_complex_infinite_p
def finite?
- ::Complex.__finite__(@real) && ::Complex.__finite__(@image)
+ ::Complex.__finite__(self.real) && ::Complex.__finite__(self.imaginary)
end
def infinite?
- return nil if !::Complex.__infinite__(@real) && !::Complex.__infinite__(@image)
+ return nil if !::Complex.__infinite__(self.real) && !::Complex.__infinite__(self.imaginary)
1
end
# nucomp_to_i / nucomp_to_f / nucomp_to_r / nucomp_rationalize
def to_i
- unless ::Complex.__exact_zero__(@image)
+ unless ::Complex.__exact_zero__(self.imaginary)
::Kernel.raise(::RangeError, "can't convert #{self} into Integer")
end
- @real.to_i
+ self.real.to_i
end
def to_f
- unless ::Complex.__exact_zero__(@image)
+ unless ::Complex.__exact_zero__(self.imaginary)
::Kernel.raise(::RangeError, "can't convert #{self} into Float")
end
- @real.to_f
+ self.real.to_f
end
def to_r
- if ::Float === @image && @image == 0.0
+ if ::Float === self.imaginary && self.imaginary == 0.0
# a Float zero imaginary part converts as well as an exact one
- elsif !::Complex.__exact_zero__(@image)
- imag = ::Rational.__check_to_rational__(@image) rescue nil
+ elsif !::Complex.__exact_zero__(self.imaginary)
+ imag = ::Rational.__check_to_rational__(self.imaginary) rescue nil
if imag.nil? || !(::Rational === imag) || !::Complex.__exact_zero__(imag)
::Kernel.raise(::RangeError, "can't convert #{self} into Rational")
end
end
- @real.to_r
+ self.real.to_r
end
def rationalize(*args)
if args.size > 1
::Kernel.raise(::ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)")
end
- unless ::Complex.__exact_zero__(@image)
+ unless ::Complex.__exact_zero__(self.imaginary)
::Kernel.raise(::RangeError, "can't convert #{self} into Rational")
end
- @real.rationalize(*args)
+ self.real.rationalize(*args)
end
def to_c
@@ -758,7 +745,7 @@ def clone(freeze: nil)
# nucomp_marshal_dump / nucomp_marshal_load (by way of Complex::compatible in MRI).
def marshal_dump
- [@real, @image]
+ [self.real, self.imaginary]
end
private :marshal_dump
@@ -767,10 +754,7 @@ def marshal_load(a)
if a.size != 2
::Kernel.raise(::ArgumentError, "marshaled complex must have an array whose length is 2 but #{a.size}")
end
- @real = a[0]
- @image = a[1]
- freeze
- self
+ __ir_set_parts__(a[0], a[1])
end
private :marshal_load
@@ -1060,7 +1044,8 @@ def self.convert(*args)
module Kernel
# nucomp_f_complex. Complex(1) is (1+0i) while Complex(1, nil) is a TypeError, so the
# arity is read off *args rather than from a default.
- def Complex(*args, exception: true)
+ # Kernel#Complex is C# (KernelOps.ToComplex) for Integers and Floats; the rest comes here.
+ def __ir_Complex_slow__(*args, exception: true)
if args.size < 1 || args.size > 2
::Kernel.raise(::ArgumentError, "wrong number of arguments (given #{args.size}, expected 1..2)")
end
@@ -1068,7 +1053,7 @@ def Complex(*args, exception: true)
return args[0] if args.size == 1 && ::Complex.equal?(::Kernel.instance_method(:class).bind(args[0]).call)
::Complex.__convert__(args[0], args[1], args.size == 2, raise_)
end
- module_function :Complex
+ module_function :__ir_Complex_slow__
end
class Numeric
@@ -1119,3 +1104,8 @@ def to_c
::Complex.__parse_string__(self, false)
end
end
+
+class Complex
+ private :__ir_add_slow__, :__ir_sub_slow__, :__ir_mul_slow__, :__ir_div_slow__,
+ :__ir_eqeq_slow__, :__ir_hash_slow__, :__ir_abs_slow__
+end