瀏覽代碼

Fix ToInt32 conversion (#902)

Joseph Da Silva 4 年之前
父節點
當前提交
21dd3fb474
共有 2 個文件被更改,包括 124 次插入6 次删除
  1. 76 0
      Jint.Tests/Runtime/TypeConverterTests.cs
  2. 48 6
      Jint/Runtime/TypeConverter.cs

+ 76 - 0
Jint.Tests/Runtime/TypeConverterTests.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using Jint.Native;
+using Jint.Runtime;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class TypeConverterTests
+    {
+
+        public static readonly IEnumerable<object[]> ConvertNumberToInt32AndUint32TestData = new TheoryData<double, int>()
+        {
+            { 0.0, 0 },
+            { -0.0, 0 },
+            { double.Epsilon, 0 },
+            { 0.5, 0 },
+            { -0.5, 0 },
+            { 0.9999999999999999, 0 },
+            { 1.0, 1 },
+            { 1.5, 1 },
+            { 10.0, 10 },
+            { -12.3, -12 },
+            { 1485772.6, 1485772 },
+            { -984737183.8, -984737183 },
+
+            { Math.Pow(2, 31) - 1.0, int.MaxValue },
+            { Math.Pow(2, 31) - 0.5, int.MaxValue },
+            { Math.Pow(2, 32) - 1.0, -1 },
+            { Math.Pow(2, 32) - 0.5, -1 },
+            { Math.Pow(2, 32), 0 },
+            { -Math.Pow(2, 32), 0 },
+            { -Math.Pow(2, 32) - 0.5, 0 },
+            { Math.Pow(2, 32) + 1.0, 1 },
+            { Math.Pow(2, 45) + 17.56, 17 },
+            { Math.Pow(2, 45) - 17.56, -18 },
+            { -Math.Pow(2, 45) + 17.56, 18 },
+            { Math.Pow(2, 51) + 17.5, 17 },
+            { Math.Pow(2, 51) - 17.5, -18 },
+
+            { Math.Pow(2, 53) - 1.0, -1 },
+            { -Math.Pow(2, 53) + 1.0, 1 },
+            { Math.Pow(2, 53), 0 },
+            { -Math.Pow(2, 53), 0 },
+            { Math.Pow(2, 53) + 12.0, 12 },
+            { -Math.Pow(2, 53) - 12.0, -12 },
+
+            { (Math.Pow(2, 53) - 1.0) * Math.Pow(2, 1), -2 },
+            { -(Math.Pow(2, 53) - 1.0) * Math.Pow(2, 3), 8 },
+            { -(Math.Pow(2, 53) - 1.0) * Math.Pow(2, 11), 1 << 11 },
+            { (Math.Pow(2, 53) - 1.0) * Math.Pow(2, 20), -(1 << 20) },
+            { (Math.Pow(2, 53) - 1.0) * Math.Pow(2, 31), int.MinValue },
+            { -(Math.Pow(2, 53) - 1.0) * Math.Pow(2, 31), int.MinValue },
+            { (Math.Pow(2, 53) - 1.0) * Math.Pow(2, 32), 0 },
+            { -(Math.Pow(2, 53) - 1.0) * Math.Pow(2, 32), 0 },
+            { (Math.Pow(2, 53) - 1.0) * Math.Pow(2, 36), 0 },
+    
+            { double.MaxValue, 0 },
+            { double.MinValue, 0 },
+            { double.PositiveInfinity, 0 },
+            { double.NegativeInfinity, 0 },
+            { double.NaN, 0 },
+        };
+
+        [Theory]
+        [MemberData(nameof(ConvertNumberToInt32AndUint32TestData))]
+        public void ConvertNumberToInt32AndUint32(double value, int expectedResult)
+        {
+            JsValue jsval = value;
+            Assert.Equal(expectedResult, TypeConverter.ToInt32(jsval));
+            Assert.Equal((uint)expectedResult, TypeConverter.ToUint32(jsval));
+        }
+
+    }
+
+}

+ 48 - 6
Jint/Runtime/TypeConverter.cs

@@ -375,14 +375,46 @@ namespace Jint.Runtime
             return (long) number;
         }
 
+        internal static int DoubleToInt32Slow(double o)
+        {
+            // Computes the integral value of the number mod 2^32.
+
+            long doubleBits = BitConverter.DoubleToInt64Bits(o);
+            int sign = (int)(doubleBits >> 63);   // 0 if positive, -1 if negative
+            int exponent = (int)((doubleBits >> 52) & 0x7FF) - 1023;
+
+            if ((uint)exponent >= 84)
+            {
+                // Anything with an exponent that is negative or >= 84 will convert to zero.
+                // This includes infinities and NaNs, which have exponent = 1024
+                // The 84 comes from 52 (bits in double mantissa) + 32 (bits in integer)
+                return 0;
+            }
+
+            long mantissa = (doubleBits & 0xFFFFFFFFFFFFFL) | 0x10000000000000L;
+            int int32Value = (exponent >= 52) ? (int)(mantissa << (exponent - 52)) : (int)(mantissa >> (52 - exponent));
+
+            return (int32Value + sign) ^ sign;
+        }
+
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5
         /// </summary>
         public static int ToInt32(JsValue o)
         {
-            return o._type == InternalTypes.Integer
-                ? o.AsInteger()
-                : (int) (uint) ToNumber(o);
+            if (o._type == InternalTypes.Integer)
+            {
+                return o.AsInteger();
+            }
+
+            double doubleVal = ToNumber(o);
+            if (doubleVal >= -(double)int.MinValue && doubleVal <= (double)int.MaxValue)
+            {
+                // Double-to-int cast is correct in this range
+                return (int)doubleVal;
+            }
+
+            return DoubleToInt32Slow(doubleVal);
         }
 
         /// <summary>
@@ -390,9 +422,19 @@ namespace Jint.Runtime
         /// </summary>
         public static uint ToUint32(JsValue o)
         {
-            return o._type == InternalTypes.Integer
-                ? (uint) o.AsInteger()
-                : (uint) ToNumber(o);
+            if (o._type == InternalTypes.Integer)
+            {
+                return (uint)o.AsInteger();
+            }
+
+            double doubleVal = ToNumber(o);
+            if (doubleVal >= 0.0 && doubleVal <= (double)uint.MaxValue)
+            {
+                // Double-to-uint cast is correct in this range
+                return (uint)doubleVal;
+            }
+
+            return (uint)DoubleToInt32Slow(doubleVal);
         }
 
         /// <summary>