Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Src/IronRuby.Tests/RubyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,7 @@ public Tests(Driver/*!*/ driver) {
Method_OptionalAndKeywordParameters1,
Method_OptionalAndKeywordParameters2,
Range_MinMaxInclude1,
Integer_Power1,
Method_LightFrame1,
Method_LightFrame2,
Hash_SelfKey1,
Expand Down
40 changes: 40 additions & 0 deletions Src/IronRuby.Tests/Runtime/MiscTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3171,6 +3171,43 @@ p r
");
}

/// <summary>
/// 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
/// Bignum exponents. The expected output is CRuby's.
/// </summary>
public void Integer_Power1() {
TestOutput(@"
require 'ruby4'
def t(l); r = begin; yield; rescue => e; [e.class, e.message]; end; puts ""#{l}: #{r.inspect}""; end
t('int') { [3 ** 5, 2 ** 62, 2 ** 64, 3 ** 0, 0 ** 0, (-2) ** 3, 10 ** 20] }
t('neg') { [3 ** -2, 1 ** -5, (-1) ** -3, (-1) ** -4, 2 ** -1, (2**70) ** -1, (-3) ** -3] }
t('zero neg') { 0 ** -1 }
t('float') { [3 ** 0.5, 4 ** 0.5, 0 ** -1.5, 0 ** Float::NAN, 1 ** Float::NAN, 2 ** Float::INFINITY, (-2) ** 2.0, (-8) ** (1.0/3), (-2) ** Float::NAN, (2**70) ** 0.5, (-(2**70)) ** 0.5] }
t('rational') { [2 ** Rational(1, 2), 4 ** Rational(1, 2), 2 ** Rational(3)] }
t('complex') { [2 ** Complex(1, 1), 2 ** Complex(2, 0)] }
t('coerce') { o = Object.new; def o.coerce(x) = [x, 3]; 2 ** o }
t('bad') { 2 ** ""x"" }
t('big exp') { [1 ** (2**70), (-1) ** (2**70), 0 ** (2**70)] }
t('big neg exp') { [2 ** -(2**70)] }
t('pow') { [3.pow(5), 3.pow(-2), 3.pow(5, 7), (2**70).pow(2)] }
t('method') { [3.method(:**).arity, 2.method(:**).owner] }
", @"
int: [243, 4611686018427387904, 18446744073709551616, 1, 1, -8, 100000000000000000000]
neg: [(1/9), 1, -1, 1, (1/2), (1/1180591620717411303424), (-1/27)]
zero neg: [ZeroDivisionError, ""divided by 0""]
float: [1.7320508075688772, 2.0, Infinity, 0.0, 1.0, Infinity, 4.0, (1.0000000000000002+1.7320508075688772i), (NaN+NaN*i), 34359738368.0, (0.0+34359738368.0i)]
rational: [1.4142135623730951, 2.0, (8/1)]
complex: [(1.5384778027279442+1.2779225526272695i), (4+0i)]
coerce: 8
bad: [TypeError, ""String can't be coerced into Integer""]
big exp: [1, 1, 0]
big neg exp: [ArgumentError, ""exponent is too large""]
pow: [243, (1/9), 5, 1393796574908163946345982392040522594123776]
method: [1, Integer]
");
}

/// <summary>
/// Range#min, #max, #include? and #member? in C#, with what they hand the prelude: an Integer or Float
/// range answered from its endpoints, Strings, blocks, counts, endless and beginless ranges, a break
Expand All @@ -3191,6 +3228,8 @@ public void Range_MinMaxInclude1() {
t('minmax') { [(1..10).minmax, (1..10).minmax { |a, b| b <=> a }] }
t('include') { r = (1..10); [r.include?(5), r.include?(10), (1...10).include?(10), r.include?(0), r.include?(5.5), r.member?(3), r.include?(""a""), (1.0..2.0).include?(1.5), (1.0...2.0).include?(2), (1..2.0).include?(2), (1.0..2.0).include?(Float::NAN), (1..Float::INFINITY).include?(10**30)] }
t('include str') { [('a'..'z').include?('m'), ('a'..'z').include?('cc'), ('a'..'z').member?('z')] }
r = ('a'..'z'); x = ('a'...'z'); d = ('0'..'9'); w = ('A'..'z')
p [r.include?('m'), r.include?('a'), r.include?('z'), x.include?('z'), r.include?('cc'), r.include?(''), r.include?('A'), r.member?('q'), d.include?('5'), w.include?('_'), w.include?('['), ('z'..'a').include?('m'), r.include?(""é""), ('a'..'e').include?(:c), r.include?('m'.b), Class.new(String).new('k').then { |s| r.include?(s) }, ('a'..'zz').include?('cc'), ('aa'..'b').include?('az')]
t('include endless') { [(1..).include?(10**20), (..5).include?(3), (1..nil).include?(0)] }
t('include obj') { (Time.at(0)..Time.at(10)).include?(Time.at(5)) }
t('include beginless str') { (..'z').include?('a') }
Expand All @@ -3209,6 +3248,7 @@ public void Range_MinMaxInclude1() {
minmax: [[1, 10], [10, 1]]
include: [true, true, false, false, true, true, false, true, false, true, false, true]
include str: [true, false, true]
[true, true, true, false, false, false, false, true, true, true, true, false, false, false, true, true, true, false]
include endless: [true, true, false]
include obj: true
include beginless str: [TypeError, ""cannot determine inclusion in beginless/endless ranges""]
Expand Down
2 changes: 1 addition & 1 deletion Src/Libraries/Builtins/Integer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ private static long LongSqrt(long n) {
[RubyMethod("pow")]
public static object Pow(BinaryOpStorage/*!*/ powStorage, int self, int other) {
if (other >= 0) {
return ClrInteger.Power(self, other);
return ClrInteger.PowerNonNegativeFixnum(self, other);
}
var site = powStorage.GetCallSite("**");
return site.Target(site, self, other);
Expand Down
14 changes: 14 additions & 0 deletions Src/Libraries/Builtins/RangeOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,20 @@ public static object Include(CallSiteStorage<Func<CallSite, object, object, obje
return ScriptingRuntimeHelpers.BooleanToObject(v >= lo && (self.ExcludeEnd ? v < hi : v <= hi));
}
}
// ('a'..'z').include?("m"): one-character ASCII endpoints walk "a".succ ... "z", so a
// one-character ASCII value is in it exactly when it lies between them (ruby4.rb's
// __string_include__ makes the same shortcut, after ~1 us of getting there).
var bs = b as MutableString;
var es = e as MutableString;
var vs = value as MutableString;
if (bs != null && es != null && vs != null && bs.GetByteCount() == 1 && es.GetByteCount() == 1 &&
bs.IsAscii() && es.IsAscii() && vs.IsAscii()) {
if (vs.GetByteCount() != 1) {
return ScriptingRuntimeHelpers.False;
}
byte lo = bs.GetByte(0), hi = es.GetByte(0), v = vs.GetByte(0);
return ScriptingRuntimeHelpers.BooleanToObject(v >= lo && (v < hi || (!self.ExcludeEnd && v == hi)));
}
var site = slow.GetCallSite("__include__", 1);
return site.Target(site, self, value);
}
Expand Down
27 changes: 19 additions & 8 deletions Src/Libraries/Extensions/ClrBigInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
* ***************************************************************************/

using System;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using IronRuby.Runtime;
using IronRuby.Runtime.Calls;
using Microsoft.Scripting.Generation;
using System.Numerics;
using Microsoft.Scripting.Runtime;
Expand Down Expand Up @@ -358,9 +360,11 @@ public static double FDiv(BigInteger/*!*/ self, [NotNull]BigInteger/*!*/ other)
/// </summary>
/// <returns>self ** exponent as Bignum/Fixnum if exponent &gt;= 0, Float otherwise</returns>
[RubyMethod("**")]
public static object Power(RubyContext/*!*/ context, BigInteger/*!*/ self, [NotNull]BigInteger/*!*/ exponent) {
public static object Power(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, RubyContext/*!*/ context,
BigInteger/*!*/ self, [NotNull]BigInteger/*!*/ exponent) {
if (exponent.Sign < 0) {
return Math.Pow(Protocols.ToDouble(self), Protocols.ToDouble(exponent));
return ClrInteger.PowerSlow(slow, Protocols.Normalize(self), exponent,
() => Math.Pow(Protocols.ToDouble(self), Protocols.ToDouble(exponent)));
}
return PowerNonNegative(self, exponent);
}
Expand All @@ -374,10 +378,10 @@ public static object Power(RubyContext/*!*/ context, BigInteger/*!*/ self, [NotN
/// Returns Float if exponent &lt; 0
/// </remarks>
[RubyMethod("**")]
public static object Power(BigInteger/*!*/ self, int exponent) {
// BigInteger doesn't handle negative exponents.
public static object Power(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, BigInteger/*!*/ self, int exponent) {
// a negative exponent makes a Rational (rational.rb)
if (exponent < 0) {
return Power(self, (double)exponent);
return ClrInteger.PowerSlow(slow, self, exponent, () => Math.Pow(Protocols.ToDouble(self), exponent));
}
return PowerNonNegative(self, exponent);
}
Expand Down Expand Up @@ -439,7 +443,11 @@ public static object Power(BigInteger/*!*/ self, int exponent) {
/// <returns>self ** exponent as Float</returns>
/// <remarks>Converts self to Float (directly) then calls System.Math.Pow</remarks>
[RubyMethod("**")]
public static object Power(BigInteger/*!*/ self, double exponent) {
public static object Power(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, BigInteger/*!*/ self, double exponent) {
if (self.Sign < 0 && ClrInteger.IsFractional(exponent)) {
// a Complex (rational.rb)
return ClrInteger.PowerSlow(slow, self, exponent, () => Math.Pow(Protocols.ToDouble(self), exponent));
}
return Math.Pow(Protocols.ToDouble(self), exponent);
}

Expand All @@ -449,8 +457,11 @@ public static object Power(BigInteger/*!*/ self, double exponent) {
/// <returns>self ** exponent</returns>
/// <remarks>Coerces self and other using other.coerce(self) then dynamically invokes **</remarks>
[RubyMethod("**")]
public static object Power(BinaryOpStorage/*!*/ coercionStorage, BinaryOpStorage/*!*/ binaryOpSite, object/*!*/ self, object exponent) {
return Protocols.CoerceAndApply(coercionStorage, binaryOpSite, "**", self, exponent);
public static object Power(BinaryOpStorage/*!*/ coercionStorage, BinaryOpStorage/*!*/ binaryOpSite,
CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, object/*!*/ self, object exponent) {
// coerced the way rational.rb's __ir_num_coerce_bin__ does it
return ClrInteger.PowerSlow(slow, self, exponent,
() => Protocols.CoerceAndApply(coercionStorage, binaryOpSite, "**", self, exponent));
}

#endregion
Expand Down
50 changes: 45 additions & 5 deletions Src/Libraries/Extensions/ClrInteger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Numerics;
using Microsoft.Scripting.Utils;
using IronRuby.Runtime;
using IronRuby.Runtime.Calls;
using Microsoft.Scripting.Generation;
using System.Runtime.CompilerServices;

Expand Down Expand Up @@ -363,8 +364,22 @@ public static double Multiply(int self, double other) {
/// Integer (Bignum or Fixnum) if other is positive
/// Float otherwise.
/// </returns>
// Integer#** answers here what it can, and leaves to rational.rb's __ir_pow_slow__ (MRI's
// rb_int_pow) what makes a Rational or a Complex - a negative exponent, a negative base to a
// fractional power - and what has to be coerced. A Ruby `def **' in front of every power
// cost ~0.1-0.9 us a call. Without the prelude the CLR answers stand, as they always did.
internal static object/*!*/ PowerSlow(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, object/*!*/ self, object other,
Func<object>/*!*/ withoutPrelude) {
var context = slow.Context;
if (!context.GetClassOf(self).ResolveMethod("__ir_pow_slow__", VisibilityContext.AllVisible).Found) {
return withoutPrelude();
}
var site = slow.GetCallSite("__ir_pow_slow__", 1);
return site.Target(site, self, other);
}

[RubyMethod("**")]
public static object/*!*/ Power(int self, int other) {
public static object/*!*/ Power(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, int self, int other) {
if (other >= 0) {
// in a long while it fits: going through BigInteger.Pow and back was most of the
// cost of a small power
Expand All @@ -376,8 +391,17 @@ public static double Multiply(int self, double other) {
} else if (self == 1) {
return One;
} else {
return Math.Pow(self, other);
return PowerSlow(slow, self, other, () => Math.Pow(self, other));
}
}

// a Fixnum to a non-negative Fixnum power (Integer#pow)
internal static object/*!*/ PowerNonNegativeFixnum(int self, int other) {
long result;
if (TryPowerInt64(self, other, out result)) {
return Narrow(result);
}
return ClrInteger.PowerNonNegative(self, other);
}

// b ** e by squaring, false as soon as a step would overflow a long
Expand Down Expand Up @@ -410,18 +434,34 @@ private static bool TryMultiplyInt64(long a, long b, out long product) {
/// </summary>
/// <returns>Float</returns>
[RubyMethod("**")]
public static double Power(int self, double other) {
public static object Power(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, int self, double other) {
if (self < 0 && IsFractional(other)) {
// a Complex
return PowerSlow(slow, self, other, () => Math.Pow(self, other));
}
// MRI's 0 ** NaN is 0.0; Math.Pow's is NaN
if (self == 0 && Double.IsNaN(other)) {
return 0.0;
}
return Math.Pow(self, other);
}

// rational.rb's Float.__ir_fractional__: NaN is, an infinity is not, and otherwise not whole
internal static bool IsFractional(double y) {
if (Double.IsNaN(y)) { return true; }
if (Double.IsInfinity(y)) { return false; }
return y != Math.Round(y);
}

/// <summary>
/// A Bignum exponent: 0, 1 and -1 have an answer, anything else is too large to compute
/// (an ArgumentError, as in MRI 3.4 and later). Without this overload the binder could
/// not choose between Bignum#** and the coercing fallback.
/// </summary>
[RubyMethod("**")]
public static object Power(RubyContext/*!*/ context, int self, [NotNull]BigInteger/*!*/ other) {
return Power(context, (BigInteger)self, other);
public static object Power(CallSiteStorage<Func<CallSite, object, object, object>>/*!*/ slow, RubyContext/*!*/ context, int self,
[NotNull]BigInteger/*!*/ other) {
return Power(slow, context, (BigInteger)self, other);
}

#endregion
Expand Down
16 changes: 8 additions & 8 deletions Src/Libraries/Initializers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4067,14 +4067,14 @@ private static void LoadIronRuby__Clr__Integer_Instance(IronRuby.Builtins.RubyMo
);

DefineLibraryMethod(module, "**", 0x51,
new[] { 0x00000004U, 0x00000000U, 0x00000000U, 0x00000000U, 0x00000000U, 0x00000000U, 0x00000004U},
new Func<IronRuby.Runtime.RubyContext, System.Numerics.BigInteger, System.Numerics.BigInteger, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<System.Numerics.BigInteger, System.Int32, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<System.Numerics.BigInteger, System.Double, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.BinaryOpStorage, IronRuby.Runtime.BinaryOpStorage, System.Object, System.Object, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<System.Int32, System.Int32, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<System.Int32, System.Double, System.Double>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.RubyContext, System.Int32, System.Numerics.BigInteger, System.Object>(IronRuby.Builtins.ClrInteger.Power)
new[] { 0x00000008U, 0x00000000U, 0x00000000U, 0x00000000U, 0x00000000U, 0x00000000U, 0x00000008U},
new Func<IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, IronRuby.Runtime.RubyContext, System.Numerics.BigInteger, System.Numerics.BigInteger, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, System.Numerics.BigInteger, System.Int32, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, System.Numerics.BigInteger, System.Double, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.BinaryOpStorage, IronRuby.Runtime.BinaryOpStorage, IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, System.Object, System.Object, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, System.Int32, System.Int32, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, System.Int32, System.Double, System.Object>(IronRuby.Builtins.ClrInteger.Power),
new Func<IronRuby.Runtime.CallSiteStorage<Func<System.Runtime.CompilerServices.CallSite, System.Object, System.Object, System.Object>>, IronRuby.Runtime.RubyContext, System.Int32, System.Numerics.BigInteger, System.Object>(IronRuby.Builtins.ClrInteger.Power)
);

DefineLibraryMethod(module, "/", 0x51,
Expand Down
6 changes: 4 additions & 2 deletions Src/StdLib/ironruby/ironruby/rational.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1031,11 +1031,12 @@ def rationalize(*args)
end

# numeric.c's rb_int_pow (fix_pow) for what makes a Rational or a Complex: a negative
# exponent and a negative base to a fractional power. The rest is the C# Integer#**.
# exponent and a negative base to a fractional power, and what has to be coerced. The C#
# Integer#** (ClrInteger.Power) answers everything else and hands these here.
alias_method :__ir_pow__, :**
private :__ir_pow__

def **(other)
def __ir_pow_slow__(other)
if ::Integer === other
return __ir_pow__(other) if other >= 0
return 1 if self == 1
Expand All @@ -1056,6 +1057,7 @@ def **(other)
__ir_num_coerce_bin__(other, :**)
end
end
private :__ir_pow_slow__
end

class Float
Expand Down
Loading