瀏覽代碼

ES6 Number APIs (#574)

Marko Lahma 6 年之前
父節點
當前提交
1f8d87a4ce
共有 33 個文件被更改,包括 2680 次插入499 次删除
  1. 0 3
      Jint.Benchmark/SunSpiderBenchmark.cs
  2. 24 0
      Jint.Tests.Test262/LanguageTests.cs
  3. 15 0
      Jint.Tests.Test262/NumberTests.cs
  4. 26 3
      Jint.Tests.Test262/test/skipped.json
  5. 5 7
      Jint.Tests/Runtime/EngineTests.cs
  6. 3 2
      Jint/Native/Array/ArrayPrototype.cs
  7. 2 2
      Jint/Native/Global/GlobalObject.cs
  8. 1 1
      Jint/Native/JsNumber.cs
  9. 1 1
      Jint/Native/JsSymbol.cs
  10. 1 1
      Jint/Native/Json/JsonSerializer.cs
  11. 656 0
      Jint/Native/Number/Dtoa/Bignum.cs
  12. 695 0
      Jint/Native/Number/Dtoa/BignumDtoa.cs
  13. 101 92
      Jint/Native/Number/Dtoa/CachePowers.cs
  14. 17 25
      Jint/Native/Number/Dtoa/DiyFp.cs
  15. 28 19
      Jint/Native/Number/Dtoa/DoubleHelper.cs
  16. 53 0
      Jint/Native/Number/Dtoa/DtoaBuilder.cs
  17. 9 0
      Jint/Native/Number/Dtoa/DtoaMode.cs
  18. 69 0
      Jint/Native/Number/Dtoa/DtoaNumberFormatter.cs
  19. 283 56
      Jint/Native/Number/Dtoa/FastDtoa.cs
  20. 0 140
      Jint/Native/Number/Dtoa/FastDtoaBuilder.cs
  21. 6 0
      Jint/Native/Number/Dtoa/NumberExtensions.cs
  22. 73 0
      Jint/Native/Number/NumberConstructor.cs
  23. 243 74
      Jint/Native/Number/NumberPrototype.cs
  24. 1 1
      Jint/Native/Object/ObjectInstance.cs
  25. 1 2
      Jint/Native/String/StringConstructor.cs
  26. 4 4
      Jint/Native/String/StringPrototype.cs
  27. 278 0
      Jint/Pooling/ConcurrentObjectPool.cs
  28. 34 45
      Jint/Pooling/StringBuilderPool.cs
  29. 2 2
      Jint/Runtime/Interpreter/Expressions/JintExpression.cs
  30. 1 2
      Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs
  31. 1 2
      Jint/Runtime/JavaScriptException.cs
  32. 46 14
      Jint/Runtime/TypeConverter.cs
  33. 1 1
      README.md

+ 0 - 3
Jint.Benchmark/SunSpiderBenchmark.cs

@@ -3,9 +3,6 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using BenchmarkDotNet.Attributes;
 using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
 using Jint;
 using Jint;
 
 
 namespace Esprima.Benchmark
 namespace Esprima.Benchmark

+ 24 - 0
Jint.Tests.Test262/LanguageTests.cs

@@ -43,5 +43,29 @@ namespace Jint.Tests.Test262
         {
         {
             RunTestInternal(sourceFile);
             RunTestInternal(sourceFile);
         }
         }
+
+        [Theory(DisplayName = "language\\source-text")]
+        [MemberData(nameof(SourceFiles), "language\\source-text", false)]
+        [MemberData(nameof(SourceFiles), "language\\source-text", true, Skip = "Skipped")]
+        protected void SourceText(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\types")]
+        [MemberData(nameof(SourceFiles), "language\\types", false)]
+        [MemberData(nameof(SourceFiles), "language\\types", true, Skip = "Skipped")]
+        protected void Types(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\white-space")]
+        [MemberData(nameof(SourceFiles), "language\\white-space", false)]
+        [MemberData(nameof(SourceFiles), "language\\white-space", true, Skip = "Skipped")]
+        protected void WhiteSpace(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
     }
     }
 }
 }

+ 15 - 0
Jint.Tests.Test262/NumberTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262
+{
+    public class NumberTests : Test262Test
+    {
+        [Theory(DisplayName = "built-ins\\Number")]
+        [MemberData(nameof(SourceFiles), "built-ins\\Number", false)]
+        [MemberData(nameof(SourceFiles), "built-ins\\Number", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 26 - 3
Jint.Tests.Test262/test/skipped.json

@@ -212,7 +212,7 @@
     "reason": "requires toLocaleString changes"
     "reason": "requires toLocaleString changes"
   },
   },
 
 
-  // experimenta
+  // experimentals start
 
 
   {
   {
     "source": "built-ins/Array/prototype/flat/array-like-objects.js",
     "source": "built-ins/Array/prototype/flat/array-like-objects.js",
@@ -278,13 +278,16 @@
     "source": "built-ins/Array/prototype/flatMap/thisArg-argument.js",
     "source": "built-ins/Array/prototype/flatMap/thisArg-argument.js",
     "reason": "experimental"
     "reason": "experimental"
   },
   },
+
+  // experimentals end
+
   {
   {
     "source": "built-ins/String/prototype/padEnd/observable-operations.js",
     "source": "built-ins/String/prototype/padEnd/observable-operations.js",
-    "reason": "observables not implemetned"
+    "reason": "observables not implemented"
   },
   },
   {
   {
     "source": "built-ins/String/prototype/padStart/observable-operations.js",
     "source": "built-ins/String/prototype/padStart/observable-operations.js",
-    "reason": "observables not implemetned"
+    "reason": "observables not implemented"
   },
   },
   {
   {
     "source": "built-ins/StringIteratorPrototype/next/next-iteration-surrogate-pairs.js",
     "source": "built-ins/StringIteratorPrototype/next/next-iteration-surrogate-pairs.js",
@@ -365,5 +368,25 @@
   {
   {
     "source": "language/expressions/template-literal/tv-line-terminator-sequence.js",
     "source": "language/expressions/template-literal/tv-line-terminator-sequence.js",
     "reason": "Line feed problems (git, windows, linux)"
     "reason": "Line feed problems (git, windows, linux)"
+  },
+  {
+    "source": "built-ins/Number/prototype/toPrecision/range.js",
+    "reason": "arrow functions not implemented"
+  },
+  {
+    "source": "built-ins/Number/prototype/toFixed/range.js",
+    "reason": "arrow functions not implemented"
+  },
+  {
+    "source": "built-ins/Number/prototype/toExponential/range.js",
+    "reason": "arrow functions not implemented"
+  },
+  {
+    "source": "language/types/number/8.5.1.js",
+    "reason": "C# can't distinguish 1.797693134862315808e+308 and 1.797693134862315708145274237317e+308"
+  },
+  {
+    "source": "language/white-space/mongolian-vowel-separator-eval.js",
+    "reason": "Esprima problem"
   }
   }
 ]
 ]

+ 5 - 7
Jint.Tests/Runtime/EngineTests.cs

@@ -6,7 +6,6 @@ using Esprima;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.Array;
-using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Debugger;
 using Jint.Runtime.Debugger;
@@ -889,8 +888,8 @@ namespace Jint.Tests.Runtime
         [Fact]
         [Fact]
         public void ShouldComputeFractionInBase()
         public void ShouldComputeFractionInBase()
         {
         {
-            Assert.Equal("011", NumberPrototype.ToFractionBase(0.375, 2));
-            Assert.Equal("14141414141414141414141414141414141414141414141414", NumberPrototype.ToFractionBase(0.375, 5));
+            Assert.Equal("011", _engine.Number.PrototypeObject.ToFractionBase(0.375, 2));
+            Assert.Equal("14141414141414141414141414141414141414141414141414", _engine.Number.PrototypeObject.ToFractionBase(0.375, 5));
         }
         }
 
 
         [Fact]
         [Fact]
@@ -965,7 +964,7 @@ namespace Jint.Tests.Runtime
         [InlineData("2qgpckvng1s", 10000000000000000L, 36)]
         [InlineData("2qgpckvng1s", 10000000000000000L, 36)]
         public void ShouldConvertNumbersToDifferentBase(string expected, long number, int radix)
         public void ShouldConvertNumbersToDifferentBase(string expected, long number, int radix)
         {
         {
-            var result = NumberPrototype.ToBase(number, radix);
+            var result = _engine.Number.PrototypeObject.ToBase(number, radix);
             Assert.Equal(expected, result);
             Assert.Equal(expected, result);
         }
         }
 
 
@@ -2053,11 +2052,10 @@ var prep = function (fn) { fn(); };
         public void ShouldStringifyNumWithoutV8DToA()
         public void ShouldStringifyNumWithoutV8DToA()
         {
         {
             // 53.6841659 cannot be converted by V8's DToA => "old" DToA code will be used.
             // 53.6841659 cannot be converted by V8's DToA => "old" DToA code will be used.
-
             var engine = new Engine();
             var engine = new Engine();
-            Native.JsValue val = engine.Execute("JSON.stringify(53.6841659)").GetCompletionValue();
+            var val = engine.Execute("JSON.stringify(53.6841659)").GetCompletionValue();
 
 
-            Assert.True(val.AsString() == "53.6841659");
+            Assert.Equal("53.6841659", val.AsString());
         }
         }
 
 
         [Fact]
         [Fact]

+ 3 - 2
Jint/Native/Array/ArrayPrototype.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Native.Symbol;
 using Jint.Pooling;
 using Jint.Pooling;
@@ -1010,7 +1011,7 @@ namespace Jint.Native.Array
                 return s;
                 return s;
             }
             }
 
 
-            using (var sb = StringBuilderPool.GetInstance())
+            using (var sb = StringBuilderPool.Rent())
             {
             {
                 sb.Builder.Append(s);
                 sb.Builder.Append(s);
                 for (uint k = 1; k < len; k++)
                 for (uint k = 1; k < len; k++)
@@ -1249,7 +1250,7 @@ namespace Jint.Native.Array
         internal abstract class ArrayOperations
         internal abstract class ArrayOperations
         {
         {
             protected internal const ulong MaxArrayLength = 4294967295;
             protected internal const ulong MaxArrayLength = 4294967295;
-            protected internal const ulong MaxArrayLikeLength = 9007199254740991;
+            protected internal const ulong MaxArrayLikeLength = NumberConstructor.MaxSafeInteger;
 
 
             public abstract ObjectInstance Target { get; }
             public abstract ObjectInstance Target { get; }
 
 

+ 2 - 2
Jint/Native/Global/GlobalObject.cs

@@ -74,7 +74,7 @@ namespace Jint.Native.Global
         /// <summary>
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.2
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.2
         /// </summary>
         /// </summary>
-        public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments)
+        public JsValue ParseInt(JsValue thisObject, JsValue[] arguments)
         {
         {
             string inputString = TypeConverter.ToString(arguments.At(0));
             string inputString = TypeConverter.ToString(arguments.At(0));
             var s = StringPrototype.TrimEx(inputString);
             var s = StringPrototype.TrimEx(inputString);
@@ -175,7 +175,7 @@ namespace Jint.Native.Global
         /// <summary>
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.3
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.3
         /// </summary>
         /// </summary>
-        public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments)
+        public JsValue ParseFloat(JsValue thisObject, JsValue[] arguments)
         {
         {
             var inputString = TypeConverter.ToString(arguments.At(0));
             var inputString = TypeConverter.ToString(arguments.At(0));
             var trimmedString = StringPrototype.TrimStartEx(inputString);
             var trimmedString = StringPrototype.TrimStartEx(inputString);

+ 1 - 1
Jint/Native/JsNumber.cs

@@ -13,7 +13,7 @@ namespace Jint.Native
         internal readonly double _value;
         internal readonly double _value;
 
 
         // how many decimals to check when determining if double is actually an int
         // how many decimals to check when determining if double is actually an int
-        private const double DoubleIsIntegerTolerance = double.Epsilon * 100;
+        internal const double DoubleIsIntegerTolerance = double.Epsilon * 100;
 
 
         private static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0);
         private static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0);
 
 

+ 1 - 1
Jint/Native/JsSymbol.cs

@@ -18,7 +18,7 @@ namespace Jint.Native
 
 
         internal JsSymbol(JsValue value) : base(Types.Symbol)
         internal JsSymbol(JsValue value) : base(Types.Symbol)
         {
         {
-            _value = value.IsUndefined() ? "" : TypeConverter.ToString(value);
+            _value = value.IsUndefined() ? "" : value.ToString();
         }
         }
 
 
         public override object ToObject()
         public override object ToObject()

+ 1 - 1
Jint/Native/Json/JsonSerializer.cs

@@ -186,7 +186,7 @@ namespace Jint.Native.Json
                 var isFinite = GlobalObject.IsFinite(Undefined.Instance, Arguments.From(value));
                 var isFinite = GlobalObject.IsFinite(Undefined.Instance, Arguments.From(value));
                 if (((JsBoolean) isFinite)._value)
                 if (((JsBoolean) isFinite)._value)
                 {
                 {
-                    return TypeConverter.ToString(value);
+                    return TypeConverter.ToString(((JsNumber) value)._value);
                 }
                 }
 
 
                 return "null";
                 return "null";

+ 656 - 0
Jint/Native/Number/Dtoa/Bignum.cs

@@ -0,0 +1,656 @@
+using System.Diagnostics;
+using Jint.Runtime;
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal sealed class Bignum
+    {
+        // 3584 = 128 * 28. We can represent 2^3584 > 10^1000 accurately.
+        // This bignum can encode much bigger numbers, since it contains an
+        // exponent.
+        private const int kMaxSignificantBits = 3584;
+
+        private const int kChunkSize = sizeof(uint) * 8;
+        private const int kDoubleChunkSize = sizeof(ulong) * 8;
+
+        // With bigit size of 28 we loose some bits, but a double still fits easily
+        // into two chunks, and more importantly we can use the Comba multiplication.
+        private const int kBigitSize = 28;
+        private const uint kBigitMask = (1 << kBigitSize) - 1;
+
+        // Every instance allocates kBigitLength chunks on the stack. Bignums cannot
+        // grow. There are no checks if the stack-allocated space is sufficient.
+        private const int kBigitCapacity = kMaxSignificantBits / kBigitSize;
+
+        private uint[] bigits_ = new uint[kBigitCapacity];
+
+        // The Bignum's value equals value(bigits_) * 2^(exponent_ * kBigitSize).
+        private int exponent_;
+        private int used_digits_;
+
+        private int BigitLength()
+        {
+            return used_digits_ + exponent_;
+        }
+
+        // Precondition: this/other < 16bit.
+        public uint DivideModuloIntBignum(Bignum other)
+        {
+            Debug.Assert(IsClamped());
+            Debug.Assert(other.IsClamped());
+            Debug.Assert(other.used_digits_ > 0);
+
+            // Easy case: if we have less digits than the divisor than the result is 0.
+            // Note: this handles the case where this == 0, too.
+            if (BigitLength() < other.BigitLength()) return 0;
+
+            Align(other);
+
+            uint result = 0;
+
+            // Start by removing multiples of 'other' until both numbers have the same
+            // number of digits.
+            while (BigitLength() > other.BigitLength())
+            {
+                // This naive approach is extremely inefficient if the this divided other
+                // might be big. This function is implemented for doubleToString where
+                // the result should be small (less than 10).
+                Debug.Assert(other.bigits_[other.used_digits_ - 1] >= (1 << kBigitSize) / 16);
+                // Remove the multiples of the first digit.
+                // Example this = 23 and other equals 9. -> Remove 2 multiples.
+                result += bigits_[used_digits_ - 1];
+                SubtractTimes(other, bigits_[used_digits_ - 1]);
+            }
+
+            Debug.Assert(BigitLength() == other.BigitLength());
+
+            // Both bignums are at the same length now.
+            // Since other has more than 0 digits we know that the access to
+            // bigits_[used_digits_ - 1] is safe.
+            var this_bigit = bigits_[used_digits_ - 1];
+            var other_bigit = other.bigits_[other.used_digits_ - 1];
+
+            if (other.used_digits_ == 1)
+            {
+                // Shortcut for easy (and common) case.
+                uint quotient = this_bigit / other_bigit;
+                bigits_[used_digits_ - 1] = this_bigit - other_bigit * quotient;
+                result += quotient;
+                Clamp();
+                return result;
+            }
+
+            uint division_estimate = this_bigit / (other_bigit + 1);
+            result += division_estimate;
+            SubtractTimes(other, division_estimate);
+
+            if (other_bigit * (division_estimate + 1) > this_bigit) return result;
+
+            while (LessEqual(other, this))
+            {
+                SubtractBignum(other);
+                result++;
+            }
+
+            return result;
+        }
+
+        void Align(Bignum other)
+        {
+            if (exponent_ > other.exponent_)
+            {
+                // If "X" represents a "hidden" digit (by the exponent) then we are in the
+                // following case (a == this, b == other):
+                // a:  aaaaaaXXXX   or a:   aaaaaXXX
+                // b:     bbbbbbX      b: bbbbbbbbXX
+                // We replace some of the hidden digits (X) of a with 0 digits.
+                // a:  aaaaaa000X   or a:   aaaaa0XX
+                int zero_digits = exponent_ - other.exponent_;
+                EnsureCapacity(used_digits_ + zero_digits);
+                for (int i = used_digits_ - 1; i >= 0; --i)
+                {
+                    bigits_[i + zero_digits] = bigits_[i];
+                }
+
+                for (int i = 0; i < zero_digits; ++i)
+                {
+                    bigits_[i] = 0;
+                }
+
+                used_digits_ += zero_digits;
+                exponent_ -= zero_digits;
+                Debug.Assert(used_digits_ >= 0);
+                Debug.Assert(exponent_ >= 0);
+            }
+        }
+
+        void EnsureCapacity(int size)
+        {
+            if (size > kBigitCapacity)
+            {
+                ExceptionHelper.ThrowInvalidOperationException();
+            }
+        }
+
+        private void Clamp()
+        {
+            while (used_digits_ > 0 && bigits_[used_digits_ - 1] == 0) used_digits_--;
+            if (used_digits_ == 0) exponent_ = 0;
+        }
+
+
+        private bool IsClamped()
+        {
+            return used_digits_ == 0 || bigits_[used_digits_ - 1] != 0;
+        }
+
+
+        private void Zero()
+        {
+            for (var i = 0; i < used_digits_; ++i) bigits_[i] = 0;
+            used_digits_ = 0;
+            exponent_ = 0;
+        }
+
+        // Guaranteed to lie in one Bigit.
+        internal void AssignUInt16(uint value)
+        {
+            Debug.Assert(kBigitSize >= 8 * sizeof(uint));
+            Zero();
+            if (value == 0) return;
+
+            EnsureCapacity(1);
+            bigits_[0] = value;
+            used_digits_ = 1;
+        }
+
+        internal void AssignUInt64(ulong value)
+        {
+            const int kUInt64Size = 64;
+
+            Zero();
+            if (value == 0) return;
+
+            int needed_bigits = kUInt64Size / kBigitSize + 1;
+            EnsureCapacity(needed_bigits);
+            for (int i = 0; i < needed_bigits; ++i)
+            {
+                bigits_[i] = (uint) (value & kBigitMask);
+                value = value >> kBigitSize;
+            }
+
+            used_digits_ = needed_bigits;
+            Clamp();
+        }
+
+
+        internal void AssignBignum(Bignum other)
+        {
+            exponent_ = other.exponent_;
+            for (int i = 0; i < other.used_digits_; ++i)
+            {
+                bigits_[i] = other.bigits_[i];
+            }
+
+            // Clear the excess digits (if there were any).
+            for (int i = other.used_digits_; i < used_digits_; ++i)
+            {
+                bigits_[i] = 0;
+            }
+
+            used_digits_ = other.used_digits_;
+        }
+
+
+        void SubtractTimes(Bignum other, uint factor)
+        {
+#if DEBUG
+            var a = new Bignum();
+            var b = new Bignum();
+            a.AssignBignum(this);
+            b.AssignBignum(other);
+            b.MultiplyByUInt32(factor);
+            a.SubtractBignum(b);
+#endif
+            Debug.Assert(exponent_ <= other.exponent_);
+            if (factor < 3)
+            {
+                for (int i = 0; i < factor; ++i)
+                {
+                    SubtractBignum(other);
+                }
+
+                return;
+            }
+
+            uint borrow = 0;
+            int exponent_diff = other.exponent_ - exponent_;
+            for (int i = 0; i < other.used_digits_; ++i)
+            {
+                ulong product = factor * other.bigits_[i];
+                ulong remove = borrow + product;
+                uint difference = bigits_[i + exponent_diff] - (uint) (remove & kBigitMask);
+                bigits_[i + exponent_diff] = difference & kBigitMask;
+                borrow = (uint) ((difference >> (kChunkSize - 1)) + (remove >> kBigitSize));
+            }
+
+            for (int i = other.used_digits_ + exponent_diff; i < used_digits_; ++i)
+            {
+                if (borrow == 0) return;
+                uint difference = bigits_[i] - borrow;
+                bigits_[i] = difference & kBigitMask;
+                borrow = difference >> (kChunkSize - 1);
+            }
+
+            Clamp();
+
+#if DEBUG
+            Debug.Assert(Equal(a, this));
+#endif
+        }
+
+
+        void SubtractBignum(Bignum other)
+        {
+            Debug.Assert(IsClamped());
+            Debug.Assert(other.IsClamped());
+            // We require this to be bigger than other.
+            Debug.Assert(LessEqual(other, this));
+
+            Align(other);
+
+            int offset = other.exponent_ - exponent_;
+            uint borrow = 0;
+            int i;
+            for (i = 0; i < other.used_digits_; ++i)
+            {
+                Debug.Assert((borrow == 0) || (borrow == 1));
+                uint difference = bigits_[i + offset] - other.bigits_[i] - borrow;
+                bigits_[i + offset] = difference & kBigitMask;
+                borrow = difference >> (kChunkSize - 1);
+            }
+
+            while (borrow != 0)
+            {
+                uint difference = bigits_[i + offset] - borrow;
+                bigits_[i + offset] = difference & kBigitMask;
+                borrow = difference >> (kChunkSize - 1);
+                ++i;
+            }
+
+            Clamp();
+        }
+
+        internal static bool Equal(Bignum a, Bignum b)
+        {
+            return Compare(a, b) == 0;
+        }
+
+        internal static bool LessEqual(Bignum a, Bignum b)
+        {
+            return Compare(a, b) <= 0;
+        }
+
+        internal static bool Less(Bignum a, Bignum b)
+        {
+            return Compare(a, b) < 0;
+        }
+
+        // Returns a + b == c
+        static bool PlusEqual(Bignum a, Bignum b, Bignum c)
+        {
+            return PlusCompare(a, b, c) == 0;
+        }
+
+        // Returns a + b <= c
+        static bool PlusLessEqual(Bignum a, Bignum b, Bignum c)
+        {
+            return PlusCompare(a, b, c) <= 0;
+        }
+
+        // Returns a + b < c
+        static bool PlusLess(Bignum a, Bignum b, Bignum c)
+        {
+            return PlusCompare(a, b, c) < 0;
+        }
+
+        uint BigitAt(int index)
+        {
+            if (index >= BigitLength()) return 0;
+            if (index < exponent_) return 0;
+            return bigits_[index - exponent_];
+        }
+
+
+        static int Compare(Bignum a, Bignum b)
+        {
+            Debug.Assert(a.IsClamped());
+            Debug.Assert(b.IsClamped());
+            int bigit_length_a = a.BigitLength();
+            int bigit_length_b = b.BigitLength();
+            if (bigit_length_a < bigit_length_b) return -1;
+            if (bigit_length_a > bigit_length_b) return +1;
+            for (int i = bigit_length_a - 1; i >= System.Math.Min(a.exponent_, b.exponent_); --i)
+            {
+                uint bigit_a = a.BigitAt(i);
+                uint bigit_b = b.BigitAt(i);
+                if (bigit_a < bigit_b) return -1;
+                if (bigit_a > bigit_b) return +1;
+                // Otherwise they are equal up to this digit. Try the next digit.
+            }
+
+            return 0;
+        }
+
+
+        internal static int PlusCompare(Bignum a, Bignum b, Bignum c)
+        {
+            Debug.Assert(a.IsClamped());
+            Debug.Assert(b.IsClamped());
+            Debug.Assert(c.IsClamped());
+            if (a.BigitLength() < b.BigitLength())
+            {
+                return PlusCompare(b, a, c);
+            }
+
+            if (a.BigitLength() + 1 < c.BigitLength()) return -1;
+            if (a.BigitLength() > c.BigitLength()) return +1;
+            // The exponent encodes 0-bigits. So if there are more 0-digits in 'a' than
+            // 'b' has digits, then the bigit-length of 'a'+'b' must be equal to the one
+            // of 'a'.
+            if (a.exponent_ >= b.BigitLength() && a.BigitLength() < c.BigitLength())
+            {
+                return -1;
+            }
+
+            uint borrow = 0;
+            // Starting at min_exponent all digits are == 0. So no need to compare them.
+            int min_exponent = System.Math.Min(System.Math.Min(a.exponent_, b.exponent_), c.exponent_);
+            for (int i = c.BigitLength() - 1; i >= min_exponent; --i)
+            {
+                uint chunk_a = a.BigitAt(i);
+                uint chunk_b = b.BigitAt(i);
+                uint chunk_c = c.BigitAt(i);
+                uint sum = chunk_a + chunk_b;
+                if (sum > chunk_c + borrow)
+                {
+                    return +1;
+                }
+                else
+                {
+                    borrow = chunk_c + borrow - sum;
+                    if (borrow > 1) return -1;
+                    borrow <<= kBigitSize;
+                }
+            }
+
+            if (borrow == 0) return 0;
+            return -1;
+        }
+
+        internal void Times10()
+        {
+            MultiplyByUInt32(10);
+        }
+
+        internal void MultiplyByUInt32(uint factor)
+        {
+            if (factor == 1) return;
+            if (factor == 0)
+            {
+                Zero();
+                return;
+            }
+
+            if (used_digits_ == 0) return;
+
+            // The product of a bigit with the factor is of size kBigitSize + 32.
+            // Assert that this number + 1 (for the carry) fits into double chunk.
+            Debug.Assert(kDoubleChunkSize >= kBigitSize + 32 + 1);
+            ulong carry = 0;
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                ulong product = ((ulong) factor) * bigits_[i] + carry;
+                bigits_[i] = (uint) (product & kBigitMask);
+                carry = (product >> kBigitSize);
+            }
+
+            while (carry != 0)
+            {
+                EnsureCapacity(used_digits_ + 1);
+                bigits_[used_digits_] = (uint) (carry & kBigitMask);
+                used_digits_++;
+                carry >>= kBigitSize;
+            }
+        }
+
+        internal void MultiplyByUInt64(ulong factor)
+        {
+            if (factor == 1) return;
+            if (factor == 0) {
+                Zero();
+                return;
+            }
+            Debug.Assert(kBigitSize < 32);
+            ulong carry = 0;
+            ulong low = factor & 0xFFFFFFFF;
+            ulong high = factor >> 32;
+            for (int i = 0; i < used_digits_; ++i) {
+                ulong product_low = low * bigits_[i];
+                ulong product_high = high * bigits_[i];
+                ulong tmp = (carry & kBigitMask) + product_low;
+                bigits_[i] = (uint) (tmp & kBigitMask);
+                carry = (carry >> kBigitSize) + (tmp >> kBigitSize) +
+                        (product_high << (32 - kBigitSize));
+            }
+            while (carry != 0) {
+                EnsureCapacity(used_digits_ + 1);
+                bigits_[used_digits_] = (uint) (carry & kBigitMask);
+                used_digits_++;
+                carry >>= kBigitSize;
+            }
+        }
+
+        internal void ShiftLeft(int shift_amount)
+        {
+            if (used_digits_ == 0) return;
+            exponent_ += shift_amount / kBigitSize;
+            int local_shift = shift_amount % kBigitSize;
+            EnsureCapacity(used_digits_ + 1);
+            BigitsShiftLeft(local_shift);
+        }
+
+        void BigitsShiftLeft(int shift_amount)
+        {
+            Debug.Assert(shift_amount < kBigitSize);
+            Debug.Assert(shift_amount >= 0);
+            uint carry = 0;
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                uint new_carry = bigits_[i] >> (kBigitSize - shift_amount);
+                bigits_[i] = ((bigits_[i] << shift_amount) + carry) & kBigitMask;
+                carry = new_carry;
+            }
+
+            if (carry != 0)
+            {
+                bigits_[used_digits_] = carry;
+                used_digits_++;
+            }
+        }
+
+
+        internal void AssignPowerUInt16(uint baseValue, int power_exponent)
+        {
+            Debug.Assert(baseValue != 0);
+            Debug.Assert(power_exponent >= 0);
+            if (power_exponent == 0)
+            {
+                AssignUInt16(1);
+                return;
+            }
+
+            Zero();
+            int shifts = 0;
+            // We expect baseValue to be in range 2-32, and most often to be 10.
+            // It does not make much sense to implement different algorithms for counting
+            // the bits.
+            while ((baseValue & 1) == 0)
+            {
+                baseValue >>= 1;
+                shifts++;
+            }
+
+            int bit_size = 0;
+            uint tmp_base = baseValue;
+            while (tmp_base != 0)
+            {
+                tmp_base >>= 1;
+                bit_size++;
+            }
+
+            int final_size = bit_size * power_exponent;
+            // 1 extra bigit for the shifting, and one for rounded final_size.
+            EnsureCapacity(final_size / kBigitSize + 2);
+
+            // Left to Right exponentiation.
+            int mask = 1;
+            while (power_exponent >= mask) mask <<= 1;
+
+            // The mask is now pointing to the bit above the most significant 1-bit of
+            // power_exponent.
+            // Get rid of first 1-bit;
+            mask >>= 2;
+            ulong this_value = baseValue;
+
+            bool delayed_multipliciation = false;
+            const ulong max_32bits = 0xFFFFFFFF;
+            while (mask != 0 && this_value <= max_32bits)
+            {
+                this_value = this_value * this_value;
+                // Verify that there is enough space in this_value to perform the
+                // multiplication.  The first bit_size bits must be 0.
+                if ((power_exponent & mask) != 0)
+                {
+                    ulong base_bits_mask = ~((((ulong) 1) << (64 - bit_size)) - 1);
+                    bool high_bits_zero = (this_value & base_bits_mask) == 0;
+                    if (high_bits_zero)
+                    {
+                        this_value *= baseValue;
+                    }
+                    else
+                    {
+                        delayed_multipliciation = true;
+                    }
+                }
+
+                mask >>= 1;
+            }
+
+            AssignUInt64(this_value);
+            if (delayed_multipliciation)
+            {
+                MultiplyByUInt32(baseValue);
+            }
+
+            // Now do the same thing as a bignum.
+            while (mask != 0)
+            {
+                Square();
+                if ((power_exponent & mask) != 0)
+                {
+                    MultiplyByUInt32(baseValue);
+                }
+
+                mask >>= 1;
+            }
+
+            // And finally add the saved shifts.
+            ShiftLeft(shifts * power_exponent);
+        }
+
+        void Square()
+        {
+            Debug.Assert(IsClamped());
+            int product_length = 2 * used_digits_;
+            EnsureCapacity(product_length);
+
+            // Comba multiplication: compute each column separately.
+            // Example: r = a2a1a0 * b2b1b0.
+            //    r =  1    * a0b0 +
+            //        10    * (a1b0 + a0b1) +
+            //        100   * (a2b0 + a1b1 + a0b2) +
+            //        1000  * (a2b1 + a1b2) +
+            //        10000 * a2b2
+            //
+            // In the worst case we have to accumulate nb-digits products of digit*digit.
+            //
+            // Assert that the additional number of bits in a DoubleChunk are enough to
+            // sum up used_digits of Bigit*Bigit.
+            if ((1 << (2 * (kChunkSize - kBigitSize))) <= used_digits_)
+            {
+                ExceptionHelper.ThrowNotImplementedException();
+            }
+
+            ulong accumulator = 0;
+            // First shift the digits so we don't overwrite them.
+            int copy_offset = used_digits_;
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                bigits_[copy_offset + i] = bigits_[i];
+            }
+
+            // We have two loops to avoid some 'if's in the loop.
+            for (int i = 0; i < used_digits_; ++i)
+            {
+                // Process temporary digit i with power i.
+                // The sum of the two indices must be equal to i.
+                int bigit_index1 = i;
+                int bigit_index2 = 0;
+                // Sum all of the sub-products.
+                while (bigit_index1 >= 0)
+                {
+                    uint chunk1 = bigits_[copy_offset + bigit_index1];
+                    uint chunk2 = bigits_[copy_offset + bigit_index2];
+                    accumulator += (ulong) chunk1 * chunk2;
+                    bigit_index1--;
+                    bigit_index2++;
+                }
+
+                bigits_[i] = (uint) accumulator & kBigitMask;
+                accumulator >>= kBigitSize;
+            }
+
+            for (int i = used_digits_; i < product_length; ++i)
+            {
+                int bigit_index1 = used_digits_ - 1;
+                int bigit_index2 = i - bigit_index1;
+                // Invariant: sum of both indices is again equal to i.
+                // Inner loop runs 0 times on last iteration, emptying accumulator.
+                while (bigit_index2 < used_digits_)
+                {
+                    uint chunk1 = bigits_[copy_offset + bigit_index1];
+                    uint chunk2 = bigits_[copy_offset + bigit_index2];
+                    accumulator += (ulong) chunk1 * chunk2;
+                    bigit_index1--;
+                    bigit_index2++;
+                }
+
+                // The overwritten bigits_[i] will never be read in further loop iterations,
+                // because bigit_index1 and bigit_index2 are always greater
+                // than i - used_digits_.
+                bigits_[i] = (uint) accumulator & kBigitMask;
+                accumulator >>= kBigitSize;
+            }
+
+            // Since the result was guaranteed to lie inside the number the
+            // accumulator must be 0 now.
+            Debug.Assert(accumulator == 0);
+
+            // Don't forget to update the used_digits and the exponent.
+            used_digits_ = product_length;
+            exponent_ *= 2;
+            Clamp();
+        }
+    }
+}

+ 695 - 0
Jint/Native/Number/Dtoa/BignumDtoa.cs

@@ -0,0 +1,695 @@
+using System;
+using System.Diagnostics;
+using Jint.Runtime;
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal static class BignumDtoa
+    {
+        public static void NumberToString(
+            double v,
+            DtoaMode mode,
+            int requested_digits,
+            DtoaBuilder builder,
+            out int decimal_point)
+        {
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            var significand = DoubleHelper.Significand(bits);
+            var is_even = (significand & 1) == 0;
+            var exponent = DoubleHelper.Exponent(bits);
+            var normalized_exponent = DoubleHelper.NormalizedExponent(significand, exponent);
+            // estimated_power might be too low by 1.
+            var estimated_power = EstimatePower(normalized_exponent);
+
+            // Shortcut for Fixed.
+            // The requested digits correspond to the digits after the point. If the
+            // number is much too small, then there is no need in trying to get any
+            // digits.
+            if (mode == DtoaMode.Fixed && -estimated_power - 1 > requested_digits)
+            {
+                // Set decimal-point to -requested_digits. This is what Gay does.
+                // Note that it should not have any effect anyways since the string is
+                // empty.
+                decimal_point = -requested_digits;
+                return;
+            }
+
+            Bignum numerator = new Bignum();
+            Bignum denominator = new Bignum();
+            Bignum delta_minus = new Bignum();
+            Bignum delta_plus = new Bignum();
+            // Make sure the bignum can grow large enough. The smallest double equals
+            // 4e-324. In this case the denominator needs fewer than 324*4 binary digits.
+            // The maximum double is 1.7976931348623157e308 which needs fewer than
+            // 308*4 binary digits.
+            var need_boundary_deltas = mode == DtoaMode.Shortest;
+
+            InitialScaledStartValues(
+                v,
+                estimated_power,
+                need_boundary_deltas,
+                numerator,
+                denominator,
+                delta_minus,
+                delta_plus);
+            // We now have v = (numerator / denominator) * 10^estimated_power.
+            FixupMultiply10(
+                estimated_power,
+                is_even,
+                out decimal_point,
+                numerator,
+                denominator,
+                delta_minus,
+                delta_plus);
+            // We now have v = (numerator / denominator) * 10^(decimal_point-1), and
+            //  1 <= (numerator + delta_plus) / denominator < 10
+            switch (mode)
+            {
+                case DtoaMode.Shortest:
+                    GenerateShortestDigits(
+                        numerator,
+                        denominator,
+                        delta_minus,
+                        delta_plus,
+                        is_even,
+                        builder);
+                    break;
+                case DtoaMode.Fixed:
+                    BignumToFixed(
+                        requested_digits,
+                        ref decimal_point,
+                        numerator,
+                        denominator,
+                        builder);
+                    break;
+                case DtoaMode.Precision:
+                    GenerateCountedDigits(
+                        requested_digits,
+                        ref decimal_point,
+                        numerator,
+                        denominator,
+                        builder);
+                    break;
+                default:
+                    ExceptionHelper.ThrowArgumentOutOfRangeException();
+                    break;
+            }
+        }
+
+
+        // The procedure starts generating digits from the left to the right and stops
+        // when the generated digits yield the shortest decimal representation of v. A
+        // decimal representation of v is a number lying closer to v than to any other
+        // double, so it converts to v when read.
+        //
+        // This is true if d, the decimal representation, is between m- and m+, the
+        // upper and lower boundaries. d must be strictly between them if !is_even.
+        //           m- := (numerator - delta_minus) / denominator
+        //           m+ := (numerator + delta_plus) / denominator
+        //
+        // Precondition: 0 <= (numerator+delta_plus) / denominator < 10.
+        //   If 1 <= (numerator+delta_plus) / denominator < 10 then no leading 0 digit
+        //   will be produced. This should be the standard precondition.
+        private static void GenerateShortestDigits(
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus,
+            bool is_even,
+            DtoaBuilder buffer)
+        {
+            // Small optimization: if delta_minus and delta_plus are the same just reuse
+            // one of the two bignums.
+            if (Bignum.Equal(delta_minus, delta_plus))
+            {
+                delta_plus = delta_minus;
+            }
+
+            int length = 0;
+            buffer.Reset();
+            while (true)
+            {
+                uint digit;
+                digit = numerator.DivideModuloIntBignum(denominator);
+                Debug.Assert(digit <= 9); // digit is a uint and therefore always positive.
+                // digit = numerator / denominator (integer division).
+                // numerator = numerator % denominator.
+                buffer.Append((char) (digit + '0'));
+
+                // Can we stop already?
+                // If the remainder of the division is less than the distance to the lower
+                // boundary we can stop. In this case we simply round down (discarding the
+                // remainder).
+                // Similarly we test if we can round up (using the upper boundary).
+                bool in_delta_room_minus;
+                bool in_delta_room_plus;
+                if (is_even)
+                {
+                    in_delta_room_minus = Bignum.LessEqual(numerator, delta_minus);
+                }
+                else
+                {
+                    in_delta_room_minus = Bignum.Less(numerator, delta_minus);
+                }
+                if (is_even)
+                {
+                    in_delta_room_plus = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0;
+                }
+                else
+                {
+                    in_delta_room_plus = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0;
+                }
+                if (!in_delta_room_minus && !in_delta_room_plus)
+                {
+                    // Prepare for next iteration.
+                    numerator.Times10();
+                    delta_minus.Times10();
+                    // We optimized delta_plus to be equal to delta_minus (if they share the
+                    // same value). So don't multiply delta_plus if they point to the same
+                    // object.
+                    if (delta_minus != delta_plus) delta_plus.Times10();
+                }
+                else if (in_delta_room_minus && in_delta_room_plus)
+                {
+                    // Let's see if 2*numerator < denominator.
+                    // If yes, then the next digit would be < 5 and we can round down.
+                    int compare = Bignum.PlusCompare(numerator, numerator, denominator);
+                    if (compare < 0)
+                    {
+                        // Remaining digits are less than .5. -> Round down (== do nothing).
+                    }
+                    else if (compare > 0)
+                    {
+                        // Remaining digits are more than .5 of denominator. . Round up.
+                        // Note that the last digit could not be a '9' as otherwise the whole
+                        // loop would have stopped earlier.
+                        // We still have an assert here in case the preconditions were not
+                        // satisfied.
+                        Debug.Assert(buffer[length - 1] != '9');
+                        buffer[length - 1]++;
+                    }
+                    else
+                    {
+                        // Halfway case.
+                        // TODO(floitsch): need a way to solve half-way cases.
+                        //   For now let's round towards even (since this is what Gay seems to
+                        //   do).
+
+                        if ((buffer[length - 1] - '0') % 2 == 0)
+                        {
+                            // Round down => Do nothing.
+                        }
+                        else
+                        {
+                            Debug.Assert(buffer[length - 1] != '9');
+                            buffer[length - 1]++;
+                        }
+                    }
+
+                    return;
+                }
+                else if (in_delta_room_minus)
+                {
+                    // Round down (== do nothing).
+                    return;
+                }
+                else
+                {
+                    // in_delta_room_plus
+                    // Round up.
+                    // Note again that the last digit could not be '9' since this would have
+                    // stopped the loop earlier.
+                    // We still have an DCHECK here, in case the preconditions were not
+                    // satisfied.
+                    Debug.Assert(buffer[length - 1] != '9');
+                    buffer[length - 1]++;
+                    return;
+                }
+            }
+        }
+        
+        // Let v = numerator / denominator < 10.
+        // Then we generate 'count' digits of d = x.xxxxx... (without the decimal point)
+        // from left to right. Once 'count' digits have been produced we decide wether
+        // to round up or down. Remainders of exactly .5 round upwards. Numbers such
+        // as 9.999999 propagate a carry all the way, and change the
+        // exponent (decimal_point), when rounding upwards.
+        static void GenerateCountedDigits(
+            int count, 
+            ref int decimal_point,
+            Bignum numerator, 
+            Bignum denominator,
+            DtoaBuilder buffer)
+        {
+            Debug.Assert(count >= 0);
+            for (int i = 0; i < count - 1; ++i)
+            {
+                uint d = numerator.DivideModuloIntBignum(denominator);
+                Debug.Assert(d <= 9);  // digit is a uint and therefore always positive.
+                // digit = numerator / denominator (integer division).
+                // numerator = numerator % denominator.
+                buffer.Append((char) (d + '0'));
+                // Prepare for next iteration.
+                numerator.Times10();
+            }
+            // Generate the last digit.
+            uint digit = numerator.DivideModuloIntBignum(denominator);
+            if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0)
+            {
+                digit++;
+            }
+            buffer.Append((char) (digit + '0'));
+            // Correct bad digits (in case we had a sequence of '9's). Propagate the
+            // carry until we hat a non-'9' or til we reach the first digit.
+            for (int i = count - 1; i > 0; --i)
+            {
+                if (buffer[i] != '0' + 10) break;
+                buffer[i] = '0';
+                buffer[i - 1]++;
+            }
+            if (buffer[0] == '0' + 10)
+            {
+                // Propagate a carry past the top place.
+                buffer[0] = '1';
+                decimal_point++;
+            }
+        }
+
+
+        // Generates 'requested_digits' after the decimal point. It might omit
+        // trailing '0's. If the input number is too small then no digits at all are
+        // generated (ex.: 2 fixed digits for 0.00001).
+        //
+        // Input verifies:  1 <= (numerator + delta) / denominator < 10.
+        static void BignumToFixed(
+            int requested_digits,
+            ref int decimal_point,
+            Bignum numerator,
+            Bignum denominator,
+            DtoaBuilder buffer)
+        {
+            // Note that we have to look at more than just the requested_digits, since
+            // a number could be rounded up. Example: v=0.5 with requested_digits=0.
+            // Even though the power of v equals 0 we can't just stop here.
+            if (-(decimal_point) > requested_digits)
+            {
+                // The number is definitively too small.
+                // Ex: 0.001 with requested_digits == 1.
+                // Set decimal-point to -requested_digits. This is what Gay does.
+                // Note that it should not have any effect anyways since the string is
+                // empty.
+                decimal_point = -requested_digits;
+                buffer.Reset();
+                return;
+            }
+
+            if (-decimal_point == requested_digits)
+            {
+                // We only need to verify if the number rounds down or up.
+                // Ex: 0.04 and 0.06 with requested_digits == 1.
+                Debug.Assert(decimal_point == -requested_digits);
+                // Initially the fraction lies in range (1, 10]. Multiply the denominator
+                // by 10 so that we can compare more easily.
+                denominator.Times10();
+                if (Bignum.PlusCompare(numerator, numerator, denominator) >= 0)
+                {
+                    // If the fraction is >= 0.5 then we have to include the rounded
+                    // digit.
+                    buffer[0] = '1';
+                    decimal_point++;
+                }
+                else
+                {
+                    // Note that we caught most of similar cases earlier.
+                    buffer.Reset();
+                }
+            }
+            else
+            {
+                // The requested digits correspond to the digits after the point.
+                // The variable 'needed_digits' includes the digits before the point.
+                int needed_digits = (decimal_point) + requested_digits;
+                GenerateCountedDigits(needed_digits, ref decimal_point, numerator, denominator, buffer);
+            }
+        }
+
+        // Returns an estimation of k such that 10^(k-1) <= v < 10^k where
+        // v = f * 2^exponent and 2^52 <= f < 2^53.
+        // v is hence a normalized double with the given exponent. The output is an
+        // approximation for the exponent of the decimal approimation .digits * 10^k.
+        //
+        // The result might undershoot by 1 in which case 10^k <= v < 10^k+1.
+        // Note: this property holds for v's upper boundary m+ too.
+        //    10^k <= m+ < 10^k+1.
+        //   (see explanation below).
+        //
+        // Examples:
+        //  EstimatePower(0)   => 16
+        //  EstimatePower(-52) => 0
+        //
+        // Note: e >= 0 => EstimatedPower(e) > 0. No similar claim can be made for e<0.
+        private static int EstimatePower(int exponent)
+        {
+            // This function estimates log10 of v where v = f*2^e (with e == exponent).
+            // Note that 10^floor(log10(v)) <= v, but v <= 10^ceil(log10(v)).
+            // Note that f is bounded by its container size. Let p = 53 (the double's
+            // significand size). Then 2^(p-1) <= f < 2^p.
+            //
+            // Given that log10(v) == log2(v)/log2(10) and e+(len(f)-1) is quite close
+            // to log2(v) the function is simplified to (e+(len(f)-1)/log2(10)).
+            // The computed number undershoots by less than 0.631 (when we compute log3
+            // and not log10).
+            //
+            // Optimization: since we only need an approximated result this computation
+            // can be performed on 64 bit integers. On x86/x64 architecture the speedup is
+            // not really measurable, though.
+            //
+            // Since we want to avoid overshooting we decrement by 1e10 so that
+            // floating-point imprecisions don't affect us.
+            //
+            // Explanation for v's boundary m+: the computation takes advantage of
+            // the fact that 2^(p-1) <= f < 2^p. Boundaries still satisfy this requirement
+            // (even for denormals where the delta can be much more important).
+
+            const double k1Log10 = 0.30102999566398114; // 1/lg(10)
+
+            // For doubles len(f) == 53 (don't forget the hidden bit).
+            const int kSignificandSize = 53;
+            double estimate = System.Math.Ceiling((exponent + kSignificandSize - 1) * k1Log10 - 1e-10);
+            return (int) estimate;
+        }
+
+
+        // See comments for InitialScaledStartValues.
+        private static void InitialScaledStartValuesPositiveExponent(
+            double v,
+            int estimated_power,
+            bool need_boundary_deltas,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            // A positive exponent implies a positive power.
+            Debug.Assert(estimated_power >= 0);
+            // Since the estimated_power is positive we simply multiply the denominator
+            // by 10^estimated_power.
+
+            // numerator = v.
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            numerator.AssignUInt64(DoubleHelper.Significand(bits));
+            numerator.ShiftLeft(DoubleHelper.Exponent(bits));
+            // denominator = 10^estimated_power.
+            denominator.AssignPowerUInt16(10, estimated_power);
+
+            if (need_boundary_deltas)
+            {
+                // Introduce a common denominator so that the deltas to the boundaries are
+                // integers.
+                denominator.ShiftLeft(1);
+                numerator.ShiftLeft(1);
+                // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
+                // denominator (of 2) delta_plus equals 2^e.
+                delta_plus.AssignUInt16(1);
+                delta_plus.ShiftLeft(DoubleHelper.Exponent(bits));
+                // Same for delta_minus (with adjustments below if f == 2^p-1).
+                delta_minus.AssignUInt16(1);
+                delta_minus.ShiftLeft(DoubleHelper.Exponent(bits));
+
+                // If the significand (without the hidden bit) is 0, then the lower
+                // boundary is closer than just half a ulp (unit in the last place).
+                // There is only one exception: if the next lower number is a denormal then
+                // the distance is 1 ulp. This cannot be the case for exponent >= 0 (but we
+                // have to test it in the other function where exponent < 0).
+                ulong v_bits = bits;
+                if ((v_bits & DoubleHelper.KSignificandMask) == 0)
+                {
+                    // The lower boundary is closer at half the distance of "normal" numbers.
+                    // Increase the common denominator and adapt all but the delta_minus.
+                    denominator.ShiftLeft(1); // *2
+                    numerator.ShiftLeft(1); // *2
+                    delta_plus.ShiftLeft(1); // *2
+                }
+            }
+        }
+
+
+        // See comments for InitialScaledStartValues
+        private static void InitialScaledStartValuesNegativeExponentPositivePower(
+            double v,
+            int estimated_power, 
+            bool need_boundary_deltas,
+            Bignum numerator, 
+            Bignum denominator,
+            Bignum delta_minus, 
+            Bignum delta_plus)
+        {
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            ulong significand = DoubleHelper.Significand(bits);
+            int exponent = DoubleHelper.Exponent(bits);
+            // v = f * 2^e with e < 0, and with estimated_power >= 0.
+            // This means that e is close to 0 (have a look at how estimated_power is
+            // computed).
+
+            // numerator = significand
+            //  since v = significand * 2^exponent this is equivalent to
+            //  numerator = v * / 2^-exponent
+            numerator.AssignUInt64(significand);
+            // denominator = 10^estimated_power * 2^-exponent (with exponent < 0)
+            denominator.AssignPowerUInt16(10, estimated_power);
+            denominator.ShiftLeft(-exponent);
+
+            if (need_boundary_deltas)
+            {
+                // Introduce a common denominator so that the deltas to the boundaries are
+                // integers.
+                denominator.ShiftLeft(1);
+                numerator.ShiftLeft(1);
+                // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
+                // denominator (of 2) delta_plus equals 2^e.
+                // Given that the denominator already includes v's exponent the distance
+                // to the boundaries is simply 1.
+                delta_plus.AssignUInt16(1);
+                // Same for delta_minus (with adjustments below if f == 2^p-1).
+                delta_minus.AssignUInt16(1);
+
+                // If the significand (without the hidden bit) is 0, then the lower
+                // boundary is closer than just one ulp (unit in the last place).
+                // There is only one exception: if the next lower number is a denormal
+                // then the distance is 1 ulp. Since the exponent is close to zero
+                // (otherwise estimated_power would have been negative) this cannot happen
+                // here either.
+                ulong v_bits = bits;
+                if ((v_bits & DoubleHelper.KSignificandMask) == 0)
+                {
+                    // The lower boundary is closer at half the distance of "normal" numbers.
+                    // Increase the denominator and adapt all but the delta_minus.
+                    denominator.ShiftLeft(1); // *2
+                    numerator.ShiftLeft(1); // *2
+                    delta_plus.ShiftLeft(1); // *2
+                }
+            }
+        }
+
+
+        // See comments for InitialScaledStartValues
+        private static void InitialScaledStartValuesNegativeExponentNegativePower(
+            double v,
+            int estimated_power,
+            bool need_boundary_deltas,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            const ulong kMinimalNormalizedExponent = 0x0010000000000000;
+
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            ulong significand = DoubleHelper.Significand(bits);
+            int exponent = DoubleHelper.Exponent(bits);
+            // Instead of multiplying the denominator with 10^estimated_power we
+            // multiply all values (numerator and deltas) by 10^-estimated_power.
+
+            // Use numerator as temporary container for power_ten.
+            Bignum power_ten = numerator;
+            power_ten.AssignPowerUInt16(10, -estimated_power);
+
+            if (need_boundary_deltas)
+            {
+                // Since power_ten == numerator we must make a copy of 10^estimated_power
+                // before we complete the computation of the numerator.
+                // delta_plus = delta_minus = 10^estimated_power
+                delta_plus.AssignBignum(power_ten);
+                delta_minus.AssignBignum(power_ten);
+            }
+
+            // numerator = significand * 2 * 10^-estimated_power
+            //  since v = significand * 2^exponent this is equivalent to
+            // numerator = v * 10^-estimated_power * 2 * 2^-exponent.
+            // Remember: numerator has been abused as power_ten. So no need to assign it
+            //  to itself.
+            Debug.Assert(numerator == power_ten);
+            numerator.MultiplyByUInt64(significand);
+
+            // denominator = 2 * 2^-exponent with exponent < 0.
+            denominator.AssignUInt16(1);
+            denominator.ShiftLeft(-exponent);
+
+            if (need_boundary_deltas)
+            {
+                // Introduce a common denominator so that the deltas to the boundaries are
+                // integers.
+                numerator.ShiftLeft(1);
+                denominator.ShiftLeft(1);
+                // With this shift the boundaries have their correct value, since
+                // delta_plus = 10^-estimated_power, and
+                // delta_minus = 10^-estimated_power.
+                // These assignments have been done earlier.
+
+                // The special case where the lower boundary is twice as close.
+                // This time we have to look out for the exception too.
+                ulong v_bits = bits;
+                if ((v_bits & DoubleHelper.KSignificandMask) == 0 &&
+                    // The only exception where a significand == 0 has its boundaries at
+                    // "normal" distances:
+                    (v_bits & DoubleHelper.KExponentMask) != kMinimalNormalizedExponent)
+                {
+                    numerator.ShiftLeft(1); // *2
+                    denominator.ShiftLeft(1); // *2
+                    delta_plus.ShiftLeft(1); // *2
+                }
+            }
+        }
+
+
+        // Let v = significand * 2^exponent.
+        // Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator
+        // and denominator. The functions GenerateShortestDigits and
+        // GenerateCountedDigits will then convert this ratio to its decimal
+        // representation d, with the required accuracy.
+        // Then d * 10^estimated_power is the representation of v.
+        // (Note: the fraction and the estimated_power might get adjusted before
+        // generating the decimal representation.)
+        //
+        // The initial start values consist of:
+        //  - a scaled numerator: s.t. numerator/denominator == v / 10^estimated_power.
+        //  - a scaled (common) denominator.
+        //  optionally (used by GenerateShortestDigits to decide if it has the shortest
+        //  decimal converting back to v):
+        //  - v - m-: the distance to the lower boundary.
+        //  - m+ - v: the distance to the upper boundary.
+        //
+        // v, m+, m-, and therefore v - m- and m+ - v all share the same denominator.
+        //
+        // Let ep == estimated_power, then the returned values will satisfy:
+        //  v / 10^ep = numerator / denominator.
+        //  v's boundarys m- and m+:
+        //    m- / 10^ep == v / 10^ep - delta_minus / denominator
+        //    m+ / 10^ep == v / 10^ep + delta_plus / denominator
+        //  Or in other words:
+        //    m- == v - delta_minus * 10^ep / denominator;
+        //    m+ == v + delta_plus * 10^ep / denominator;
+        //
+        // Since 10^(k-1) <= v < 10^k    (with k == estimated_power)
+        //  or       10^k <= v < 10^(k+1)
+        //  we then have 0.1 <= numerator/denominator < 1
+        //           or    1 <= numerator/denominator < 10
+        //
+        // It is then easy to kickstart the digit-generation routine.
+        //
+        // The boundary-deltas are only filled if need_boundary_deltas is set.
+        private static void InitialScaledStartValues(
+            double v,
+            int estimated_power,
+            bool need_boundary_deltas,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            var bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            if (DoubleHelper.Exponent(bits) >= 0)
+            {
+                InitialScaledStartValuesPositiveExponent(
+                    v,
+                    estimated_power,
+                    need_boundary_deltas,
+                    numerator,
+                    denominator,
+                    delta_minus,
+                    delta_plus);
+            }
+            else if (estimated_power >= 0)
+            {
+                InitialScaledStartValuesNegativeExponentPositivePower(
+                    v,
+                    estimated_power,
+                    need_boundary_deltas,
+                    numerator,
+                    denominator,
+                    delta_minus,
+                    delta_plus);
+            }
+            else
+            {
+                InitialScaledStartValuesNegativeExponentNegativePower(
+                    v,
+                    estimated_power,
+                    need_boundary_deltas,
+                    numerator,
+                    denominator,
+                    delta_minus,
+                    delta_plus);
+            }
+        }
+
+
+        // This routine multiplies numerator/denominator so that its values lies in the
+        // range 1-10. That is after a call to this function we have:
+        //    1 <= (numerator + delta_plus) /denominator < 10.
+        // Let numerator the input before modification and numerator' the argument
+        // after modification, then the output-parameter decimal_point is such that
+        //  numerator / denominator * 10^estimated_power ==
+        //    numerator' / denominator' * 10^(decimal_point - 1)
+        // In some cases estimated_power was too low, and this is already the case. We
+        // then simply adjust the power so that 10^(k-1) <= v < 10^k (with k ==
+        // estimated_power) but do not touch the numerator or denominator.
+        // Otherwise the routine multiplies the numerator and the deltas by 10.
+        private static void FixupMultiply10(
+            int estimated_power,
+            bool is_even,
+            out int decimal_point,
+            Bignum numerator,
+            Bignum denominator,
+            Bignum delta_minus,
+            Bignum delta_plus)
+        {
+            bool in_range;
+            if (is_even)
+                in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) >= 0;
+            else
+                in_range = Bignum.PlusCompare(numerator, delta_plus, denominator) > 0;
+            if (in_range)
+            {
+                // Since numerator + delta_plus >= denominator we already have
+                // 1 <= numerator/denominator < 10. Simply update the estimated_power.
+                decimal_point = estimated_power + 1;
+            }
+            else
+            {
+                decimal_point = estimated_power;
+                numerator.Times10();
+                if (Bignum.Equal(delta_minus, delta_plus))
+                {
+                    delta_minus.Times10();
+                    delta_plus.AssignBignum(delta_minus);
+                }
+                else
+                {
+                    delta_minus.Times10();
+                    delta_plus.Times10();
+                }
+            }
+        }
+    }
+}

+ 101 - 92
Jint/Native/Number/Dtoa/CachePowers.cs

@@ -38,13 +38,13 @@ namespace Jint.Native.Number.Dtoa
 
 
         private class CachedPower
         private class CachedPower
         {
         {
-            internal readonly long Significand;
+            internal readonly ulong Significand;
             internal readonly short BinaryExponent;
             internal readonly short BinaryExponent;
             internal readonly short DecimalExponent;
             internal readonly short DecimalExponent;
 
 
             internal CachedPower(ulong significand, short binaryExponent, short decimalExponent)
             internal CachedPower(ulong significand, short binaryExponent, short decimalExponent)
             {
             {
-                Significand = (long) significand;
+                Significand =  significand;
                 BinaryExponent = binaryExponent;
                 BinaryExponent = binaryExponent;
                 DecimalExponent = decimalExponent;
                 DecimalExponent = decimalExponent;
             }
             }
@@ -62,15 +62,19 @@ namespace Jint.Native.Number.Dtoa
             internal readonly DiyFp cMk;
             internal readonly DiyFp cMk;
         }
         }
 
 
-        internal static GetCachedPowerResult GetCachedPower(int e, int alpha, int gamma)
+        internal static GetCachedPowerResult GetCachedPowerForBinaryExponentRange(int min_exponent, int max_exponent)
         {
         {
             const int kQ = DiyFp.KSignificandSize;
             const int kQ = DiyFp.KSignificandSize;
-            double k = System.Math.Ceiling((alpha - e + kQ - 1) * Kd1Log210);
-            int index = (GrisuCacheOffset + (int) k - 1) / CachedPowersSpacing + 1;
+            double k = System.Math.Ceiling((min_exponent + kQ - 1) * Kd1Log210);
+            int foo = kCachedPowersOffset;
+            int index =
+                (foo + (int) k - 1) / kDecimalExponentDistance + 1;
+            Debug.Assert(0 <= index && index < CACHED_POWERS.Length);
             CachedPower cachedPower = CACHED_POWERS[index];
             CachedPower cachedPower = CACHED_POWERS[index];
+            Debug.Assert(min_exponent <= cachedPower.BinaryExponent);
+            Debug.Assert(cachedPower.BinaryExponent <= max_exponent);
 
 
             var cMk = new DiyFp(cachedPower.Significand, cachedPower.BinaryExponent);
             var cMk = new DiyFp(cachedPower.Significand, cachedPower.BinaryExponent);
-            Debug.Assert((alpha <= cMk.E + e) && (cMk.E + e <= gamma));
             return new GetCachedPowerResult(cachedPower.DecimalExponent, cMk);
             return new GetCachedPowerResult(cachedPower.DecimalExponent, cMk);
         }
         }
 
 
@@ -78,95 +82,100 @@ namespace Jint.Native.Number.Dtoa
         // Regexp to convert this from original C++ source:
         // Regexp to convert this from original C++ source:
         // \{GRISU_UINT64_C\((\w+), (\w+)\), (\-?\d+), (\-?\d+)\}
         // \{GRISU_UINT64_C\((\w+), (\w+)\), (\-?\d+), (\-?\d+)\}
 
 
-        // interval between entries  of the powers cache below
-        private const int CachedPowersSpacing = 8;
-
         private static readonly CachedPower[] CACHED_POWERS =
         private static readonly CachedPower[] CACHED_POWERS =
         {
         {
-            new CachedPower(0xe61acf033d1a45dfL, -1087, -308),
-            new CachedPower(0xab70fe17c79ac6caL, -1060, -300),
-            new CachedPower(0xff77b1fcbebcdc4fL, -1034, -292),
-            new CachedPower(0xbe5691ef416bd60cL, -1007, -284),
-            new CachedPower(0x8dd01fad907ffc3cL, -980, -276),
-            new CachedPower(0xd3515c2831559a83L, -954, -268),
-            new CachedPower(0x9d71ac8fada6c9b5L, -927, -260),
-            new CachedPower(0xea9c227723ee8bcbL, -901, -252),
-            new CachedPower(0xaecc49914078536dL, -874, -244),
-            new CachedPower(0x823c12795db6ce57L, -847, -236),
-            new CachedPower(0xc21094364dfb5637L, -821, -228),
-            new CachedPower(0x9096ea6f3848984fL, -794, -220),
-            new CachedPower(0xd77485cb25823ac7L, -768, -212),
-            new CachedPower(0xa086cfcd97bf97f4L, -741, -204),
-            new CachedPower(0xef340a98172aace5L, -715, -196),
-            new CachedPower(0xb23867fb2a35b28eL, -688, -188),
-            new CachedPower(0x84c8d4dfd2c63f3bL, -661, -180),
-            new CachedPower(0xc5dd44271ad3cdbaL, -635, -172),
-            new CachedPower(0x936b9fcebb25c996L, -608, -164),
-            new CachedPower(0xdbac6c247d62a584L, -582, -156),
-            new CachedPower(0xa3ab66580d5fdaf6L, -555, -148),
-            new CachedPower(0xf3e2f893dec3f126L, -529, -140),
-            new CachedPower(0xb5b5ada8aaff80b8L, -502, -132),
-            new CachedPower(0x87625f056c7c4a8bL, -475, -124),
-            new CachedPower(0xc9bcff6034c13053L, -449, -116),
-            new CachedPower(0x964e858c91ba2655L, -422, -108),
-            new CachedPower(0xdff9772470297ebdL, -396, -100),
-            new CachedPower(0xa6dfbd9fb8e5b88fL, -369, -92),
-            new CachedPower(0xf8a95fcf88747d94L, -343, -84),
-            new CachedPower(0xb94470938fa89bcfL, -316, -76),
-            new CachedPower(0x8a08f0f8bf0f156bL, -289, -68),
-            new CachedPower(0xcdb02555653131b6L, -263, -60),
-            new CachedPower(0x993fe2c6d07b7facL, -236, -52),
-            new CachedPower(0xe45c10c42a2b3b06L, -210, -44),
-            new CachedPower(0xaa242499697392d3L, -183, -36),
-            new CachedPower(0xfd87b5f28300ca0eL, -157, -28),
-            new CachedPower(0xbce5086492111aebL, -130, -20),
-            new CachedPower(0x8cbccc096f5088ccL, -103, -12),
-            new CachedPower(0xd1b71758e219652cL, -77, -4),
-            new CachedPower(0x9c40000000000000L, -50, 4),
-            new CachedPower(0xe8d4a51000000000L, -24, 12),
-            new CachedPower(0xad78ebc5ac620000L, 3, 20),
-            new CachedPower(0x813f3978f8940984L, 30, 28),
-            new CachedPower(0xc097ce7bc90715b3L, 56, 36),
-            new CachedPower(0x8f7e32ce7bea5c70L, 83, 44),
-            new CachedPower(0xd5d238a4abe98068L, 109, 52),
-            new CachedPower(0x9f4f2726179a2245L, 136, 60),
-            new CachedPower(0xed63a231d4c4fb27L, 162, 68),
-            new CachedPower(0xb0de65388cc8ada8L, 189, 76),
-            new CachedPower(0x83c7088e1aab65dbL, 216, 84),
-            new CachedPower(0xc45d1df942711d9aL, 242, 92),
-            new CachedPower(0x924d692ca61be758L, 269, 100),
-            new CachedPower(0xda01ee641a708deaL, 295, 108),
-            new CachedPower(0xa26da3999aef774aL, 322, 116),
-            new CachedPower(0xf209787bb47d6b85L, 348, 124),
-            new CachedPower(0xb454e4a179dd1877L, 375, 132),
-            new CachedPower(0x865b86925b9bc5c2L, 402, 140),
-            new CachedPower(0xc83553c5c8965d3dL, 428, 148),
-            new CachedPower(0x952ab45cfa97a0b3L, 455, 156),
-            new CachedPower(0xde469fbd99a05fe3L, 481, 164),
-            new CachedPower(0xa59bc234db398c25L, 508, 172),
-            new CachedPower(0xf6c69a72a3989f5cL, 534, 180),
-            new CachedPower(0xb7dcbf5354e9beceL, 561, 188),
-            new CachedPower(0x88fcf317f22241e2L, 588, 196),
-            new CachedPower(0xcc20ce9bd35c78a5L, 614, 204),
-            new CachedPower(0x98165af37b2153dfL, 641, 212),
-            new CachedPower(0xe2a0b5dc971f303aL, 667, 220),
-            new CachedPower(0xa8d9d1535ce3b396L, 694, 228),
-            new CachedPower(0xfb9b7cd9a4a7443cL, 720, 236),
-            new CachedPower(0xbb764c4ca7a44410L, 747, 244),
-            new CachedPower(0x8bab8eefb6409c1aL, 774, 252),
-            new CachedPower(0xd01fef10a657842cL, 800, 260),
-            new CachedPower(0x9b10a4e5e9913129L, 827, 268),
-            new CachedPower(0xe7109bfba19c0c9dL, 853, 276),
-            new CachedPower(0xac2820d9623bf429L, 880, 284),
-            new CachedPower(0x80444b5e7aa7cf85L, 907, 292),
-            new CachedPower(0xbf21e44003acdd2dL, 933, 300),
-            new CachedPower(0x8e679c2f5e44ff8fL, 960, 308),
-            new CachedPower(0xd433179d9c8cb841L, 986, 316),
-            new CachedPower(0x9e19db92b4e31ba9L, 1013, 324),
-            new CachedPower(0xeb96bf6ebadf77d9L, 1039, 332),
-            new CachedPower(0xaf87023b9bf0ee6bL, 1066, 340)
+            new CachedPower(0xFA8FD5A0081C0288, -1220, -348),
+            new CachedPower(0xBAAEE17FA23EBF76, -1193, -340),
+            new CachedPower(0x8B16FB203055AC76, -1166, -332),
+            new CachedPower(0xCF42894A5DCE35EA, -1140, -324),
+            new CachedPower(0x9A6BB0AA55653B2D, -1113, -316),
+            new CachedPower(0xE61ACF033D1A45DF, -1087, -308),
+            new CachedPower(0xAB70FE17C79AC6CA, -1060, -300),
+            new CachedPower(0xFF77B1FCBEBCDC4F, -1034, -292),
+            new CachedPower(0xBE5691EF416BD60C, -1007, -284),
+            new CachedPower(0x8DD01FAD907FFC3C, -980, -276),
+            new CachedPower(0xD3515C2831559A83, -954, -268),
+            new CachedPower(0x9D71AC8FADA6C9B5, -927, -260),
+            new CachedPower(0xEA9C227723EE8BCB, -901, -252),
+            new CachedPower(0xAECC49914078536D, -874, -244),
+            new CachedPower(0x823C12795DB6CE57, -847, -236),
+            new CachedPower(0xC21094364DFB5637, -821, -228),
+            new CachedPower(0x9096EA6F3848984F, -794, -220),
+            new CachedPower(0xD77485CB25823AC7, -768, -212),
+            new CachedPower(0xA086CFCD97BF97F4, -741, -204),
+            new CachedPower(0xEF340A98172AACE5, -715, -196),
+            new CachedPower(0xB23867FB2A35B28E, -688, -188),
+            new CachedPower(0x84C8D4DFD2C63F3B, -661, -180),
+            new CachedPower(0xC5DD44271AD3CDBA, -635, -172),
+            new CachedPower(0x936B9FCEBB25C996, -608, -164),
+            new CachedPower(0xDBAC6C247D62A584, -582, -156),
+            new CachedPower(0xA3AB66580D5FDAF6, -555, -148),
+            new CachedPower(0xF3E2F893DEC3F126, -529, -140),
+            new CachedPower(0xB5B5ADA8AAFF80B8, -502, -132),
+            new CachedPower(0x87625F056C7C4A8B, -475, -124),
+            new CachedPower(0xC9BCFF6034C13053, -449, -116),
+            new CachedPower(0x964E858C91BA2655, -422, -108),
+            new CachedPower(0xDFF9772470297EBD, -396, -100),
+            new CachedPower(0xA6DFBD9FB8E5B88F, -369, -92),
+            new CachedPower(0xF8A95FCF88747D94, -343, -84),
+            new CachedPower(0xB94470938FA89BCF, -316, -76),
+            new CachedPower(0x8A08F0F8BF0F156B, -289, -68),
+            new CachedPower(0xCDB02555653131B6, -263, -60),
+            new CachedPower(0x993FE2C6D07B7FAC, -236, -52),
+            new CachedPower(0xE45C10C42A2B3B06, -210, -44),
+            new CachedPower(0xAA242499697392D3, -183, -36),
+            new CachedPower(0xFD87B5F28300CA0E, -157, -28),
+            new CachedPower(0xBCE5086492111AEB, -130, -20),
+            new CachedPower(0x8CBCCC096F5088CC, -103, -12),
+            new CachedPower(0xD1B71758E219652C, -77, -4),
+            new CachedPower(0x9C40000000000000, -50, 4),
+            new CachedPower(0xE8D4A51000000000, -24, 12),
+            new CachedPower(0xAD78EBC5AC620000, 3, 20),
+            new CachedPower(0x813F3978F8940984, 30, 28),
+            new CachedPower(0xC097CE7BC90715B3, 56, 36),
+            new CachedPower(0x8F7E32CE7BEA5C70, 83, 44),
+            new CachedPower(0xD5D238A4ABE98068, 109, 52),
+            new CachedPower(0x9F4F2726179A2245, 136, 60),
+            new CachedPower(0xED63A231D4C4FB27, 162, 68),
+            new CachedPower(0xB0DE65388CC8ADA8, 189, 76),
+            new CachedPower(0x83C7088E1AAB65DB, 216, 84),
+            new CachedPower(0xC45D1DF942711D9A, 242, 92),
+            new CachedPower(0x924D692CA61BE758, 269, 100),
+            new CachedPower(0xDA01EE641A708DEA, 295, 108),
+            new CachedPower(0xA26DA3999AEF774A, 322, 116),
+            new CachedPower(0xF209787BB47D6B85, 348, 124),
+            new CachedPower(0xB454E4A179DD1877, 375, 132),
+            new CachedPower(0x865B86925B9BC5C2, 402, 140),
+            new CachedPower(0xC83553C5C8965D3D, 428, 148),
+            new CachedPower(0x952AB45CFA97A0B3, 455, 156),
+            new CachedPower(0xDE469FBD99A05FE3, 481, 164),
+            new CachedPower(0xA59BC234DB398C25, 508, 172),
+            new CachedPower(0xF6C69A72A3989F5C, 534, 180),
+            new CachedPower(0xB7DCBF5354E9BECE, 561, 188),
+            new CachedPower(0x88FCF317F22241E2, 588, 196),
+            new CachedPower(0xCC20CE9BD35C78A5, 614, 204),
+            new CachedPower(0x98165AF37B2153DF, 641, 212),
+            new CachedPower(0xE2A0B5DC971F303A, 667, 220),
+            new CachedPower(0xA8D9D1535CE3B396, 694, 228),
+            new CachedPower(0xFB9B7CD9A4A7443C, 720, 236),
+            new CachedPower(0xBB764C4CA7A44410, 747, 244),
+            new CachedPower(0x8BAB8EEFB6409C1A, 774, 252),
+            new CachedPower(0xD01FEF10A657842C, 800, 260),
+            new CachedPower(0x9B10A4E5E9913129, 827, 268),
+            new CachedPower(0xE7109BFBA19C0C9D, 853, 276),
+            new CachedPower(0xAC2820D9623BF429, 880, 284),
+            new CachedPower(0x80444B5E7AA7CF85, 907, 292),
+            new CachedPower(0xBF21E44003ACDD2D, 933, 300),
+            new CachedPower(0x8E679C2F5E44FF8F, 960, 308),
+            new CachedPower(0xD433179D9C8CB841, 986, 316),
+            new CachedPower(0x9E19DB92B4E31BA9, 1013, 324),
+            new CachedPower(0xEB96BF6EBADF77D9, 1039, 332),
+            new CachedPower(0xAF87023B9BF0EE6B, 1066, 340)
         };
         };
 
 
-        private const int GrisuCacheOffset = 308;
+        const int kCachedPowersOffset = 348;  // -1 * the first decimal_exponent.
+        const int kDecimalExponentDistance = 8;
+        const int kMinDecimalExponent = -348;
+        const int kMaxDecimalExponent = 340;
     }
     }
 }
 }

+ 17 - 25
Jint/Native/Number/Dtoa/DiyFp.cs

@@ -43,28 +43,21 @@ namespace Jint.Native.Number.Dtoa
         internal const int KSignificandSize = 64;
         internal const int KSignificandSize = 64;
         private const ulong KUint64MSB = 0x8000000000000000L;
         private const ulong KUint64MSB = 0x8000000000000000L;
 
 
-        internal DiyFp(long f, int e)
+        internal DiyFp(ulong f, int e)
         {
         {
             F = f;
             F = f;
             E = e;
             E = e;
         }
         }
 
 
-        public readonly long F;
+        public readonly ulong F;
         public readonly int E;
         public readonly int E;
 
 
-        private static bool Uint64Gte(long a, long b)
-        {
-            // greater-or-equal for unsigned int64 in java-style...
-            return (a == b) || ((a > b) ^ (a < 0) ^ (b < 0));
-        }
-
         // Returns a - b.
         // Returns a - b.
         // The exponents of both numbers must be the same and this must be bigger
         // The exponents of both numbers must be the same and this must be bigger
         // than other. The result will not be normalized.
         // than other. The result will not be normalized.
         internal static DiyFp Minus(in DiyFp a, in DiyFp b)
         internal static DiyFp Minus(in DiyFp a, in DiyFp b)
         {
         {
             Debug.Assert(a.E == b.E);
             Debug.Assert(a.E == b.E);
-            Debug.Assert(Uint64Gte(a.F, b.F));
 
 
             return new DiyFp(a.F - b.F, a.E);
             return new DiyFp(a.F - b.F, a.E);
         }
         }
@@ -74,39 +67,38 @@ namespace Jint.Native.Number.Dtoa
         // returns a * b;
         // returns a * b;
         internal static DiyFp Times(in DiyFp a, in DiyFp b)
         internal static DiyFp Times(in DiyFp a, in DiyFp b)
         {
         {
-            DiyFp result = new DiyFp(a.F, a.E);
             // Simply "emulates" a 128 bit multiplication.
             // Simply "emulates" a 128 bit multiplication.
             // However: the resulting number only contains 64 bits. The least
             // However: the resulting number only contains 64 bits. The least
             // significant 64 bits are only used for rounding the most significant 64
             // significant 64 bits are only used for rounding the most significant 64
             // bits.
             // bits.
-            const long kM32 = 0xFFFFFFFFL;
-            long a1 = result.F.UnsignedShift(32);
-            long b1 = result.F & kM32;
-            long c = b.F.UnsignedShift(32);
-            long d = b.F & kM32;
-            long ac = a1*c;
-            long bc = b1*c;
-            long ad = a1*d;
-            long bd = b1*d;
-            long tmp = bd.UnsignedShift(32) + (ad & kM32) + (bc & kM32);
+            const ulong kM32 = 0xFFFFFFFFL;
+            ulong a1 = a.F >> 32;
+            ulong b1 = a.F & kM32;
+            ulong c = b.F >> 32;
+            ulong d = b.F & kM32;
+            ulong ac = a1*c;
+            ulong bc = b1*c;
+            ulong ad = a1*d;
+            ulong bd = b1*d;
+            ulong tmp = (bd >> 32) + (ad & kM32) + (bc & kM32);
             // By adding 1U << 31 to tmp we round the final result.
             // By adding 1U << 31 to tmp we round the final result.
             // Halfway cases will be round up.
             // Halfway cases will be round up.
             tmp += 1L << 31;
             tmp += 1L << 31;
-            long resultF = ac + ad.UnsignedShift(32) + bc.UnsignedShift(32) + tmp.UnsignedShift(32);
-            return new DiyFp(resultF, result.E + b.E + 64);
+            ulong resultF = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32);
+            return new DiyFp(resultF, a.E + b.E + 64);
         }
         }
 
 
-        internal static DiyFp Normalize(long f, int e)
+        internal static DiyFp Normalize(ulong f, int e)
         {
         {
             // This method is mainly called for normalizing boundaries. In general
             // This method is mainly called for normalizing boundaries. In general
             // boundaries need to be shifted by 10 bits. We thus optimize for this case.
             // boundaries need to be shifted by 10 bits. We thus optimize for this case.
-            const long k10MsBits = 0xFFC00000L << 32;
+            const ulong k10MsBits = (ulong) 0x3FF << 54;
             while ((f & k10MsBits) == 0)
             while ((f & k10MsBits) == 0)
             {
             {
                 f <<= 10;
                 f <<= 10;
                 e -= 10;
                 e -= 10;
             }
             }
-            while (((ulong) f & KUint64MSB) == 0)
+            while ((f & KUint64MSB) == 0)
             {
             {
                 f <<= 1;
                 f <<= 1;
                 e--;
                 e--;

+ 28 - 19
Jint/Native/Number/Dtoa/DoubleHelper.cs

@@ -32,25 +32,25 @@ using System.Diagnostics;
 
 
 namespace Jint.Native.Number.Dtoa
 namespace Jint.Native.Number.Dtoa
 {
 {
-
-// Helper functions for doubles.
+    /// <summary>
+    /// Helper functions for doubles.
+    /// </summary>
     internal class DoubleHelper
     internal class DoubleHelper
     {
     {
+        internal const ulong KExponentMask = 0x7FF0000000000000L;
+        internal const ulong KSignificandMask = 0x000FFFFFFFFFFFFFL;
+        private const ulong KHiddenBit = 0x0010000000000000L;
 
 
-        private const long KExponentMask = 0x7FF0000000000000L;
-        private const long KSignificandMask = 0x000FFFFFFFFFFFFFL;
-        private const long KHiddenBit = 0x0010000000000000L;
-
-        private static DiyFp AsDiyFp(long d64)
+        private static DiyFp AsDiyFp(ulong d64)
         {
         {
             Debug.Assert(!IsSpecial(d64));
             Debug.Assert(!IsSpecial(d64));
             return new DiyFp(Significand(d64), Exponent(d64));
             return new DiyFp(Significand(d64), Exponent(d64));
         }
         }
 
 
         // this->Significand() must not be 0.
         // this->Significand() must not be 0.
-        internal static DiyFp AsNormalizedDiyFp(long d64)
+        internal static DiyFp AsNormalizedDiyFp(ulong d64)
         {
         {
-            long f = Significand(d64);
+            ulong f = Significand(d64);
             int e = Exponent(d64);
             int e = Exponent(d64);
 
 
             Debug.Assert(f != 0);
             Debug.Assert(f != 0);
@@ -67,7 +67,7 @@ namespace Jint.Native.Number.Dtoa
             return new DiyFp(f, e);
             return new DiyFp(f, e);
         }
         }
 
 
-        private static int Exponent(long d64)
+        internal static int Exponent(ulong d64)
         {
         {
             if (IsDenormal(d64)) return KDenormalExponent;
             if (IsDenormal(d64)) return KDenormalExponent;
 
 
@@ -75,28 +75,37 @@ namespace Jint.Native.Number.Dtoa
             return biasedE - KExponentBias;
             return biasedE - KExponentBias;
         }
         }
 
 
-        private static long Significand(long d64)
+        internal static int NormalizedExponent(ulong significand, int exponent)
         {
         {
-            long significand = d64 & KSignificandMask;
-            if (!IsDenormal(d64))
+            Debug.Assert(significand != 0);
+            while ((significand & KHiddenBit) == 0)
             {
             {
-                return significand + KHiddenBit;
+                significand = significand << 1;
+                exponent = exponent - 1;
             }
             }
-            else
+            return exponent;
+        }
+
+        internal static ulong Significand(ulong d64)
+        {
+            ulong significand = d64 & KSignificandMask;
+            if (!IsDenormal(d64))
             {
             {
-                return significand;
+                return significand + KHiddenBit;
             }
             }
+
+            return significand;
         }
         }
 
 
         // Returns true if the double is a denormal.
         // Returns true if the double is a denormal.
-        private static bool IsDenormal(long d64)
+        private static bool IsDenormal(ulong d64)
         {
         {
             return (d64 & KExponentMask) == 0L;
             return (d64 & KExponentMask) == 0L;
         }
         }
 
 
         // We consider denormals not to be special.
         // We consider denormals not to be special.
         // Hence only Infinity and NaN are special.
         // Hence only Infinity and NaN are special.
-        private static bool IsSpecial(long d64)
+        private static bool IsSpecial(ulong d64)
         {
         {
             return (d64 & KExponentMask) == KExponentMask;
             return (d64 & KExponentMask) == KExponentMask;
         }
         }
@@ -116,7 +125,7 @@ namespace Jint.Native.Number.Dtoa
         // Returns the two boundaries of first argument.
         // Returns the two boundaries of first argument.
         // The bigger boundary (m_plus) is normalized. The lower boundary has the same
         // The bigger boundary (m_plus) is normalized. The lower boundary has the same
         // exponent as m_plus.
         // exponent as m_plus.
-        internal static NormalizedBoundariesResult NormalizedBoundaries(long d64)
+        internal static NormalizedBoundariesResult NormalizedBoundaries(ulong d64)
         {
         {
             DiyFp v = AsDiyFp(d64);
             DiyFp v = AsDiyFp(d64);
             bool significandIsZero = (v.F == KHiddenBit);
             bool significandIsZero = (v.F == KHiddenBit);

+ 53 - 0
Jint/Native/Number/Dtoa/DtoaBuilder.cs

@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal class DtoaBuilder
+    {
+        // allocate buffer for generated digits + extra notation + padding zeroes
+        internal readonly char[] _chars;
+        internal int Length;
+
+        public DtoaBuilder(int size)
+        {
+            _chars = new char[size];
+        }
+
+        public DtoaBuilder() : this(FastDtoa.KFastDtoaMaximalLength + 8)
+        {
+        }
+
+        internal void Append(char c)
+        {
+            _chars[Length++] = c;
+        }
+
+        internal void DecreaseLast()
+        {
+            _chars[Length - 1]--;
+        }
+
+        public void Reset()
+        {
+            Length = 0;
+            System.Array.Clear(_chars, 0, _chars.Length);
+        }
+
+        public char this[int i]
+        {
+            get => _chars[i];
+            set
+            {
+                _chars[i] = value;
+                Length = System.Math.Max(Length, i + 1);
+            }
+        }
+
+        public override string ToString()
+        {
+            return "[chars:" + new string(_chars, 0, Length) + "]";
+        }
+    }
+}

+ 9 - 0
Jint/Native/Number/Dtoa/DtoaMode.cs

@@ -0,0 +1,9 @@
+namespace Jint.Native.Number.Dtoa
+{
+    internal enum DtoaMode
+    {
+        Shortest,
+        Precision,
+        Fixed
+    }
+}

+ 69 - 0
Jint/Native/Number/Dtoa/DtoaNumberFormatter.cs

@@ -0,0 +1,69 @@
+using System.Diagnostics;
+using Jint.Runtime;
+
+namespace Jint.Native.Number.Dtoa
+{
+    internal static class DtoaNumberFormatter
+    {
+        public static void DoubleToAscii(
+            DtoaBuilder buffer,
+            double v,
+            DtoaMode mode,
+            int requested_digits,
+            out bool negative,
+            out int point)
+        {
+            Debug.Assert(!double.IsNaN(v));
+            Debug.Assert(!double.IsInfinity(v));
+            Debug.Assert(mode == DtoaMode.Shortest || requested_digits >= 0);
+
+            point = 0;
+            negative = false;
+            buffer.Reset();
+
+            if (v < 0)
+            {
+                negative = true;
+                v = -v;
+            } 
+
+            if (v == 0)
+            {
+                buffer[0] = '0';
+                point = 1;
+                return;
+            }
+
+            if (mode == DtoaMode.Precision && requested_digits == 0)
+            {
+                return;
+            }
+
+            bool fast_worked = false;
+            switch (mode) {
+                case DtoaMode.Shortest:
+                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Shortest, 0, out point, buffer);
+                    break;
+                case DtoaMode.Fixed:
+                    //fast_worked = FastFixedDtoa(v, requested_digits, buffer, length, point);
+                    ExceptionHelper.ThrowNotImplementedException();
+                    break;
+                case DtoaMode.Precision:
+                    fast_worked = FastDtoa.NumberToString(v, DtoaMode.Precision, requested_digits, out point, buffer);
+                    break;
+                default:
+                    ExceptionHelper.ThrowArgumentOutOfRangeException<string>();
+                    return;
+            }
+
+            if (fast_worked)
+            {
+                return;
+            }
+
+            // If the fast dtoa didn't succeed use the slower bignum version.
+            buffer.Reset();
+            BignumDtoa.NumberToString(v, mode, requested_digits, buffer, out point);
+       }
+    }
+}

+ 283 - 56
Jint/Native/Number/Dtoa/FastDtoa.cs

@@ -31,15 +31,12 @@
 using System;
 using System;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Threading;
 using System.Threading;
+using Jint.Runtime;
 
 
 namespace Jint.Native.Number.Dtoa
 namespace Jint.Native.Number.Dtoa
 {
 {
-    internal class FastDtoa
+    internal sealed class FastDtoa
     {
     {
-        // share buffer to reduce memory usage
-        private static readonly ThreadLocal<FastDtoaBuilder> cachedBuffer = new ThreadLocal<FastDtoaBuilder>(() => new FastDtoaBuilder());
-
-
         // FastDtoa will produce at most kFastDtoaMaximalLength digits.
         // FastDtoa will produce at most kFastDtoaMaximalLength digits.
         public const int KFastDtoaMaximalLength = 17;
         public const int KFastDtoaMaximalLength = 17;
 
 
@@ -66,15 +63,16 @@ namespace Jint.Native.Number.Dtoa
         // Output: returns true if the buffer is guaranteed to contain the closest
         // Output: returns true if the buffer is guaranteed to contain the closest
         //    representable number to the input.
         //    representable number to the input.
         //  Modifies the generated digits in the buffer to approach (round towards) w.
         //  Modifies the generated digits in the buffer to approach (round towards) w.
-        private static bool RoundWeed(FastDtoaBuilder buffer,
-            long distanceTooHighW,
-            long unsafeInterval,
-            long rest,
-            long tenKappa,
-            long unit)
+        private static bool RoundWeed(
+            DtoaBuilder buffer,
+            ulong distanceTooHighW,
+            ulong unsafeInterval,
+            ulong rest,
+            ulong tenKappa,
+            ulong unit)
         {
         {
-            long smallDistance = distanceTooHighW - unit;
-            long bigDistance = distanceTooHighW + unit;
+            ulong smallDistance = distanceTooHighW - unit;
+            ulong bigDistance = distanceTooHighW + unit;
             // Let w_low  = too_high - big_distance, and
             // Let w_low  = too_high - big_distance, and
             //     w_high = too_high - small_distance.
             //     w_high = too_high - small_distance.
             // Note: w_low < w < w_high
             // Note: w_low < w < w_high
@@ -172,6 +170,71 @@ namespace Jint.Native.Number.Dtoa
             return (2*unit <= rest) && (rest <= unsafeInterval - 4*unit);
             return (2*unit <= rest) && (rest <= unsafeInterval - 4*unit);
         }
         }
 
 
+        // Rounds the buffer upwards if the result is closer to v by possibly adding
+        // 1 to the buffer. If the precision of the calculation is not sufficient to
+        // round correctly, return false.
+        // The rounding might shift the whole buffer in which case the kappa is
+        // adjusted. For example "99", kappa = 3 might become "10", kappa = 4.
+        //
+        // If 2*rest > ten_kappa then the buffer needs to be round up.
+        // rest can have an error of +/- 1 unit. This function accounts for the
+        // imprecision and returns false, if the rounding direction cannot be
+        // unambiguously determined.
+        //
+        // Precondition: rest < ten_kappa.
+        static bool RoundWeedCounted(
+            DtoaBuilder buffer,
+            ulong rest,
+            ulong ten_kappa,
+            ulong unit,
+            ref int kappa)
+        {
+            Debug.Assert(rest < ten_kappa);
+            // The following tests are done in a specific order to avoid overflows. They
+            // will work correctly with any uint64 values of rest < ten_kappa and unit.
+            //
+            // If the unit is too big, then we don't know which way to round. For example
+            // a unit of 50 means that the real number lies within rest +/- 50. If
+            // 10^kappa == 40 then there is no way to tell which way to round.
+            if (unit >= ten_kappa) return false;
+            // Even if unit is just half the size of 10^kappa we are already completely
+            // lost. (And after the previous test we know that the expression will not
+            // over/underflow.)
+            if (ten_kappa - unit <= unit) return false;
+            // If 2 * (rest + unit) <= 10^kappa we can safely round down.
+            if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit))
+            {
+                return true;
+            }
+
+            // If 2 * (rest - unit) >= 10^kappa, then we can safely round up.
+            if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit)))
+            {
+                // Increment the last digit recursively until we find a non '9' digit.
+                buffer._chars[buffer.Length - 1]++;
+                for (int i = buffer.Length - 1; i > 0; --i)
+                {
+                    if (buffer._chars[i] != '0' + 10) break;
+                    buffer._chars[i] = '0';
+                    buffer._chars[i - 1]++;
+                }
+
+                // If the first digit is now '0'+ 10 we had a buffer with all '9's. With the
+                // exception of the first digit all digits are now '0'. Simply switch the
+                // first digit to '1' and adjust the kappa. Example: "99" becomes "10" and
+                // the power (the kappa) is increased.
+                if (buffer._chars[0] == '0' + 10)
+                {
+                    buffer._chars[0] = '1';
+                    kappa += 1;
+                }
+
+                return true;
+            }
+
+            return false;
+        }
+
         private const int KTen4 = 10000;
         private const int KTen4 = 10000;
         private const int KTen5 = 100000;
         private const int KTen5 = 100000;
         private const int KTen6 = 1000000;
         private const int KTen6 = 1000000;
@@ -184,10 +247,8 @@ namespace Jint.Native.Number.Dtoa
         // If number_bits == 0 then 0^-1 is returned
         // If number_bits == 0 then 0^-1 is returned
         // The number of bits must be <= 32.
         // The number of bits must be <= 32.
         // Precondition: (1 << number_bits) <= number < (1 << (number_bits + 1)).
         // Precondition: (1 << number_bits) <= number < (1 << (number_bits + 1)).
-        private static long BiggestPowerTen(int number,
-            int numberBits)
+        private static void BiggestPowerTen(uint number, int numberBits, out uint power, out int exponent)
         {
         {
-            int power, exponent;
             switch (numberBits)
             switch (numberBits)
             {
             {
                 case 32:
                 case 32:
@@ -304,7 +365,6 @@ namespace Jint.Native.Number.Dtoa
                     // UNREACHABLE();
                     // UNREACHABLE();
                     break;
                     break;
             }
             }
-            return ((long) power << 32) | (0xffffffffL & exponent);
         }
         }
 
 
         // Generates the digits of input number w.
         // Generates the digits of input number w.
@@ -353,8 +413,9 @@ namespace Jint.Native.Number.Dtoa
             in DiyFp low,
             in DiyFp low,
             in DiyFp w,
             in DiyFp w,
             in DiyFp high,
             in DiyFp high,
-            FastDtoaBuilder buffer,
-            int mk)
+            DtoaBuilder buffer,
+            int mk,
+            out int kappa)
         {
         {
             // low, w and high are imprecise, but by less than one ulp (unit in the last
             // low, w and high are imprecise, but by less than one ulp (unit in the last
             // place).
             // place).
@@ -367,7 +428,7 @@ namespace Jint.Native.Number.Dtoa
             // We will now start by generating the digits within the uncertain
             // We will now start by generating the digits within the uncertain
             // interval. Later we will weed out representations that lie outside the safe
             // interval. Later we will weed out representations that lie outside the safe
             // interval and thus _might_ lie outside the correct interval.
             // interval and thus _might_ lie outside the correct interval.
-            long unit = 1;
+            ulong unit = 1;
             var tooLow = new DiyFp(low.F - unit, low.E);
             var tooLow = new DiyFp(low.F - unit, low.E);
             var tooHigh = new DiyFp(high.F + unit, high.E);
             var tooHigh = new DiyFp(high.F + unit, high.E);
             // too_low and too_high are guaranteed to lie outside the interval we want the
             // too_low and too_high are guaranteed to lie outside the interval we want the
@@ -380,39 +441,44 @@ namespace Jint.Native.Number.Dtoa
             // such that:   too_low < buffer * 10^kappa < too_high
             // such that:   too_low < buffer * 10^kappa < too_high
             // We use too_high for the digit_generation and stop as soon as possible.
             // We use too_high for the digit_generation and stop as soon as possible.
             // If we stop early we effectively round down.
             // If we stop early we effectively round down.
-            var one = new DiyFp(1L << -w.E, w.E);
+            var one = new DiyFp(((ulong) 1) << -w.E, w.E);
             // Division by one is a shift.
             // Division by one is a shift.
-            var integrals = (int) (tooHigh.F.UnsignedShift(-one.E) & 0xffffffffL);
+            var integrals = (uint) (tooHigh.F.UnsignedShift(-one.E) & 0xffffffffL);
             // Modulo by one is an and.
             // Modulo by one is an and.
-            long fractionals = tooHigh.F & (one.F - 1);
-            long result = BiggestPowerTen(integrals, DiyFp.KSignificandSize - (-one.E));
-            var divider = (int) (result.UnsignedShift(32) & 0xffffffffL);
-            var dividerExponent = (int) (result & 0xffffffffL);
-            var kappa = dividerExponent + 1;
+            ulong fractionals = tooHigh.F & (one.F - 1);
+            BiggestPowerTen(
+                integrals,
+                DiyFp.KSignificandSize - (-one.E),
+                out var divider,
+                out var dividerExponent);
+
+            kappa = dividerExponent + 1;
             // Loop invariant: buffer = too_high / 10^kappa  (integer division)
             // Loop invariant: buffer = too_high / 10^kappa  (integer division)
             // The invariant holds for the first iteration: kappa has been initialized
             // The invariant holds for the first iteration: kappa has been initialized
             // with the divider exponent + 1. And the divider is the biggest power of ten
             // with the divider exponent + 1. And the divider is the biggest power of ten
             // that is smaller than integrals.
             // that is smaller than integrals.
             while (kappa > 0)
             while (kappa > 0)
             {
             {
-                int digit = integrals/divider;
+                int digit = (int) (integrals/divider);
                 buffer.Append((char) ('0' + digit));
                 buffer.Append((char) ('0' + digit));
                 integrals %= divider;
                 integrals %= divider;
                 kappa--;
                 kappa--;
                 // Note that kappa now equals the exponent of the divider and that the
                 // Note that kappa now equals the exponent of the divider and that the
                 // invariant thus holds again.
                 // invariant thus holds again.
-                long rest =
-                    ((long) integrals << -one.E) + fractionals;
+                ulong rest = ((ulong) integrals << -one.E) + fractionals;
                 // Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
                 // Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
                 // Reminder: unsafe_interval.e() == one.e()
                 // Reminder: unsafe_interval.e() == one.e()
                 if (rest < unsafeInterval.F)
                 if (rest < unsafeInterval.F)
                 {
                 {
                     // Rounding down (by not emitting the remaining digits) yields a number
                     // Rounding down (by not emitting the remaining digits) yields a number
                     // that lies within the unsafe interval.
                     // that lies within the unsafe interval.
-                    buffer.Point = buffer.End - mk + kappa;
-                    return RoundWeed(buffer, DiyFp.Minus(tooHigh, w).F,
-                        unsafeInterval.F, rest,
-                        (long) divider << -one.E, unit);
+                    return RoundWeed(
+                        buffer,
+                        DiyFp.Minus(tooHigh, w).F,
+                        unsafeInterval.F,
+                        rest,
+                        (ulong) divider << -one.E,
+                        unit);
                 }
                 }
                 divider /= 10;
                 divider /= 10;
             }
             }
@@ -444,13 +510,114 @@ namespace Jint.Native.Number.Dtoa
                 kappa--;
                 kappa--;
                 if (fractionals < unsafeInterval.F)
                 if (fractionals < unsafeInterval.F)
                 {
                 {
-                    buffer.Point = buffer.End - mk + kappa;
-                    return RoundWeed(buffer, DiyFp.Minus(tooHigh, w).F*unit,
-                        unsafeInterval.F, fractionals, one.F, unit);
+                    return RoundWeed(
+                        buffer,
+                        DiyFp.Minus(tooHigh, w).F*unit,
+                        unsafeInterval.F,
+                        fractionals,
+                        one.F,
+                        unit);
                 }
                 }
             }
             }
         }
         }
 
 
+        // Generates (at most) requested_digits of input number w.
+        // w is a floating-point number (DiyFp), consisting of a significand and an
+        // exponent. Its exponent is bounded by kMinimalTargetExponent and
+        // kMaximalTargetExponent.
+        //       Hence -60 <= w.e() <= -32.
+        //
+        // Returns false if it fails, in which case the generated digits in the buffer
+        // should not be used.
+        // Preconditions:
+        //  * w is correct up to 1 ulp (unit in the last place). That
+        //    is, its error must be strictly less than a unit of its last digit.
+        //  * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
+        //
+        // Postconditions: returns false if procedure fails.
+        //   otherwise:
+        //     * buffer is not null-terminated, but length contains the number of
+        //       digits.
+        //     * the representation in buffer is the most precise representation of
+        //       requested_digits digits.
+        //     * buffer contains at most requested_digits digits of w. If there are less
+        //       than requested_digits digits then some trailing '0's have been removed.
+        //     * kappa is such that
+        //            w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2.
+        //
+        // Remark: This procedure takes into account the imprecision of its input
+        //   numbers. If the precision is not enough to guarantee all the postconditions
+        //   then false is returned. This usually happens rarely, but the failure-rate
+        //   increases with higher requested_digits.
+        static bool DigitGenCounted(
+            in DiyFp w,
+            int requested_digits,
+            DtoaBuilder buffer,
+            out int kappa)
+        {
+            Debug.Assert(MinimalTargetExponent <= w.E && w.E <= MaximalTargetExponent);
+
+            // w is assumed to have an error less than 1 unit. Whenever w is scaled we
+            // also scale its error.
+            ulong w_error = 1;
+            // We cut the input number into two parts: the integral digits and the
+            // fractional digits. We don't emit any decimal separator, but adapt kappa
+            // instead. Example: instead of writing "1.2" we put "12" into the buffer and
+            // increase kappa by 1.
+            DiyFp one = new DiyFp(((ulong) 1) << -w.E, w.E);
+            // Division by one is a shift.
+            uint integrals = (uint) (w.F >> -one.E);
+            // Modulo by one is an and.
+            ulong fractionals = w.F & (one.F - 1);
+            BiggestPowerTen(integrals, DiyFp.KSignificandSize - (-one.E), out var divisor, out var divisor_exponent);
+            kappa = divisor_exponent + 1;
+
+            // Loop invariant: buffer = w / 10^kappa  (integer division)
+            // The invariant holds for the first iteration: kappa has been initialized
+            // with the divisor exponent + 1. And the divisor is the biggest power of ten
+            // that is smaller than 'integrals'.
+            while (kappa > 0)
+            {
+                int digit = (int) (integrals / divisor);
+                buffer.Append((char) ('0' + digit));
+                requested_digits--;
+                integrals %= divisor;
+                kappa--;
+                // Note that kappa now equals the exponent of the divisor and that the
+                // invariant thus holds again.
+                if (requested_digits == 0) break;
+                divisor /= 10;
+            }
+
+            if (requested_digits == 0)
+            {
+                ulong rest = (((ulong) integrals) << -one.E) + fractionals;
+                return RoundWeedCounted(buffer, rest,(ulong) divisor << -one.E, w_error, ref kappa);
+            }
+
+          // The integrals have been generated. We are at the point of the decimal
+          // separator. In the following loop we simply multiply the remaining digits by
+          // 10 and divide by one. We just need to pay attention to multiply associated
+          // data (the 'unit'), too.
+          // Note that the multiplication by 10 does not overflow, because w.e >= -60
+          // and thus one.e >= -60.
+          Debug.Assert(one.E >= -60);
+          Debug.Assert(fractionals < one.F);
+
+          while (requested_digits > 0 && fractionals > w_error) {
+            fractionals *= 10;
+            w_error *= 10;
+            // Integer division by one.
+            int digit = (int) (fractionals >> -one.E);
+            buffer.Append((char) ('0' + digit));
+            requested_digits--;
+            fractionals &= one.F - 1;  // Modulo by one.
+            (kappa)--;
+          }
+          if (requested_digits != 0) return false;
+          return RoundWeedCounted(buffer, fractionals, one.F, w_error, ref kappa);
+        }
+
         // Provides a decimal representation of v.
         // Provides a decimal representation of v.
         // Returns true if it succeeds, otherwise the result cannot be trusted.
         // Returns true if it succeeds, otherwise the result cannot be trusted.
         // There will be *length digits inside the buffer (not null-terminated).
         // There will be *length digits inside the buffer (not null-terminated).
@@ -462,9 +629,9 @@ namespace Jint.Native.Number.Dtoa
         // The last digit will be closest to the actual v. That is, even if several
         // The last digit will be closest to the actual v. That is, even if several
         // digits might correctly yield 'v' when read again, the closest will be
         // digits might correctly yield 'v' when read again, the closest will be
         // computed.
         // computed.
-        private static bool Grisu3(double v, FastDtoaBuilder buffer)
+        private static bool Grisu3(double v, DtoaBuilder buffer, out int decimal_exponent)
         {
         {
-            long bits = BitConverter.DoubleToInt64Bits(v);
+            ulong bits = (ulong) BitConverter.DoubleToInt64Bits(v);
             DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
             DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
             // boundary_minus and boundary_plus are the boundaries between v and its
             // boundary_minus and boundary_plus are the boundaries between v and its
             // closest floating-point neighbors. Any number strictly between
             // closest floating-point neighbors. Any number strictly between
@@ -476,9 +643,9 @@ namespace Jint.Native.Number.Dtoa
 
 
             Debug.Assert(boundaryPlus.E == w.E);
             Debug.Assert(boundaryPlus.E == w.E);
 
 
-            var result = CachedPowers.GetCachedPower(
-                w.E + DiyFp.KSignificandSize,
-                MinimalTargetExponent, MaximalTargetExponent);
+            var result = CachedPowers.GetCachedPowerForBinaryExponentRange(
+                MinimalTargetExponent - (w.E + DiyFp.KSignificandSize),
+                MaximalTargetExponent - (w.E + DiyFp.KSignificandSize));
 
 
             var mk = result.decimalExponent;
             var mk = result.decimalExponent;
             var tenMk = result.cMk;
             var tenMk = result.cMk;
@@ -513,30 +680,90 @@ namespace Jint.Native.Number.Dtoa
             // integer than it will be updated. For instance if scaled_w == 1.23 then
             // integer than it will be updated. For instance if scaled_w == 1.23 then
             // the buffer will be filled with "123" und the decimal_exponent will be
             // the buffer will be filled with "123" und the decimal_exponent will be
             // decreased by 2.
             // decreased by 2.
-            return DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, mk);
+            int kappa;
+            var digitGen = DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, mk, out kappa);
+            decimal_exponent = -mk + kappa;
+            return digitGen;
         }
         }
 
 
-        public static bool Dtoa(double v, FastDtoaBuilder buffer)
+
+        // The "counted" version of grisu3 (see above) only generates requested_digits
+        // number of digits. This version does not generate the shortest representation,
+        // and with enough requested digits 0.1 will at some point print as 0.9999999...
+        // Grisu3 is too imprecise for real halfway cases (1.5 will not work) and
+        // therefore the rounding strategy for halfway cases is irrelevant.
+        static bool Grisu3Counted(
+            double v,
+            int requested_digits,
+            DtoaBuilder buffer,
+            out int decimal_exponent)
         {
         {
-            Debug.Assert(v > 0);
-            Debug.Assert(!Double.IsNaN(v));
-            Debug.Assert(!Double.IsInfinity(v));
+            ulong bits = (ulong) BitConverter.DoubleToInt64Bits(v);
+            DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
 
 
-            return Grisu3(v, buffer);
+            var powerResult = CachedPowers.GetCachedPowerForBinaryExponentRange(
+                MinimalTargetExponent - (w.E + DiyFp.KSignificandSize),
+                MaximalTargetExponent - (w.E + DiyFp.KSignificandSize));
+
+            var mk = powerResult.decimalExponent;
+            var ten_mk = powerResult.cMk;
+            
+            Debug.Assert((MinimalTargetExponent <= w.E + ten_mk.E + DiyFp.KSignificandSize) && (MaximalTargetExponent >= w.E + ten_mk.E + DiyFp.KSignificandSize));
+            // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
+            // 64 bit significand and ten_mk is thus only precise up to 64 bits.
+
+            // The DiyFp::Times procedure rounds its result, and ten_mk is approximated
+            // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
+            // off by a small amount.
+            // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
+            // In other words: let f = scaled_w.f() and e = scaled_w.e(), then
+            //           (f-1) * 2^e < w*10^k < (f+1) * 2^e
+            DiyFp scaled_w = DiyFp.Times(w, ten_mk);
+
+            // We now have (double) (scaled_w * 10^-mk).
+            // DigitGen will generate the first requested_digits digits of scaled_w and
+            // return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
+            // will not always be exactly the same since DigitGenCounted only produces a
+            // limited number of digits.)
+            bool result = DigitGenCounted(scaled_w, requested_digits, buffer, out var kappa);
+            decimal_exponent = -mk + kappa;
+            return result;
         }
         }
 
 
-        public static string NumberToString(double v)
+        public static bool NumberToString(
+            double v,
+            DtoaMode mode,
+            int requested_digits,
+            out int decimal_point,
+            DtoaBuilder buffer)
         {
         {
-            cachedBuffer.Value.Reset();
-            if (v < 0)
+            Debug.Assert(v > 0);
+            Debug.Assert(!double.IsNaN(v));
+            Debug.Assert(!double.IsInfinity(v));
+
+            bool result;
+            int decimal_exponent = 0;
+            switch (mode)
+            {
+                case DtoaMode.Shortest:
+                    result = Grisu3(v, buffer, out decimal_exponent);
+                    break;
+                case DtoaMode.Precision:
+                    result = Grisu3Counted(v, requested_digits, buffer, out decimal_exponent);
+                    break;
+                default:
+                    result = ExceptionHelper.ThrowArgumentOutOfRangeException<bool>();
+                    break;
+            }
+
+            if (result)
             {
             {
-                cachedBuffer.Value.Append('-');
-                v = -v;
+                decimal_point = buffer.Length + decimal_exponent;
+                return true;
             }
             }
 
 
-            var numberToString = Dtoa(v, cachedBuffer.Value);
-            var toString = numberToString ? cachedBuffer.Value.Format() : null;
-            return toString;
+            decimal_point = -1;
+            return false;
         }
         }
     }
     }
 }
 }

+ 0 - 140
Jint/Native/Number/Dtoa/FastDtoaBuilder.cs

@@ -1,140 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-namespace Jint.Native.Number.Dtoa
-{
-    internal class FastDtoaBuilder
-    {
-
-        // allocate buffer for generated digits + extra notation + padding zeroes
-        private readonly char[] _chars = new char[FastDtoa.KFastDtoaMaximalLength + 8];
-        internal int End;
-        internal int Point;
-        private bool _formatted;
-
-        internal void Append(char c)
-        {
-            _chars[End++] = c;
-        }
-
-        internal void DecreaseLast()
-        {
-            _chars[End - 1]--;
-        }
-
-        public void Reset()
-        {
-            Point = 0;
-            End = 0;
-            _formatted = false;
-            System.Array.Clear(_chars, 0, _chars.Length);
-        }
-
-        public override string ToString()
-        {
-            return "[chars:" + new string(_chars, 0, End) + ", point:" + Point + "]";
-        }
-
-        public string Format()
-        {
-            if (!_formatted)
-            {
-                // check for minus sign
-                int firstDigit = _chars[0] == '-' ? 1 : 0;
-                int decPoint = Point - firstDigit;
-                if (decPoint < -5 || decPoint > 21)
-                {
-                    ToExponentialFormat(firstDigit, decPoint);
-                }
-                else
-                {
-                    ToFixedFormat(firstDigit, decPoint);
-                }
-                _formatted = true;
-            }
-            return new string(_chars, 0, End);
-
-        }
-
-        private void ToFixedFormat(int firstDigit, int decPoint)
-        {
-            if (Point < End)
-            {
-                // insert decimal point
-                if (decPoint > 0)
-                {
-                    // >= 1, split decimals and insert point
-                    System.Array.Copy(_chars, Point, _chars, Point + 1, End - Point);
-                    _chars[Point] = '.';
-                    End++;
-                }
-                else
-                {
-                    // < 1,
-                    int target = firstDigit + 2 - decPoint;
-                    System.Array.Copy(_chars, firstDigit, _chars, target, End - firstDigit);
-                    _chars[firstDigit] = '0';
-                    _chars[firstDigit + 1] = '.';
-                    if (decPoint < 0)
-                    {
-                        Fill(_chars, firstDigit + 2, target, '0');
-                    }
-                    End += 2 - decPoint;
-                }
-            }
-            else if (Point > End)
-            {
-                // large integer, add trailing zeroes
-                Fill(_chars, End, Point, '0');
-                End += Point - End;
-            }
-        }
-
-        private void ToExponentialFormat(int firstDigit, int decPoint)
-        {
-            if (End - firstDigit > 1)
-            {
-                // insert decimal point if more than one digit was produced
-                int dot = firstDigit + 1;
-                System.Array.Copy(_chars, dot, _chars, dot + 1, End - dot);
-                _chars[dot] = '.';
-                End++;
-            }
-            _chars[End++] = 'e';
-            char sign = '+';
-            int exp = decPoint - 1;
-            if (exp < 0)
-            {
-                sign = '-';
-                exp = -exp;
-            }
-            _chars[End++] = sign;
-
-            int charPos = exp > 99 ? End + 2 : exp > 9 ? End + 1 : End;
-            End = charPos + 1;
-
-            // code below is needed because Integer.getChars() is not public
-            for (;;)
-            {
-                int r = exp%10;
-                _chars[charPos--] = Digits[r];
-                exp = exp/10;
-                if (exp == 0) break;
-            }
-        }
-
-        private static readonly char[] Digits =
-        {
-            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
-        };
-
-        private static void Fill(char[] array, int fromIndex, int toIndex, char val)
-        {
-            for (int i = fromIndex; i < toIndex; i++)
-            {
-                array[i] = val;
-            }
-        }
-    }
-}

+ 6 - 0
Jint/Native/Number/Dtoa/NumberExtensions.cs

@@ -8,6 +8,12 @@ namespace Jint.Native.Number.Dtoa
         public static long UnsignedShift(this long l, int shift)
         public static long UnsignedShift(this long l, int shift)
         {
         {
             return (long) ((ulong) l >> shift);
             return (long) ((ulong) l >> shift);
+        }        
+        
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ulong UnsignedShift(this ulong l, int shift)
+        {
+            return l >> shift;
         }
         }
     }
     }
 }
 }

+ 73 - 0
Jint/Native/Number/NumberConstructor.cs

@@ -2,11 +2,15 @@
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
 
 
 namespace Jint.Native.Number
 namespace Jint.Native.Number
 {
 {
     public sealed class NumberConstructor : FunctionInstance, IConstructor
     public sealed class NumberConstructor : FunctionInstance, IConstructor
     {
     {
+        private const long MinSafeInteger = -9007199254740991;
+        internal const long MaxSafeInteger = 9007199254740991;
+
         public NumberConstructor(Engine engine)
         public NumberConstructor(Engine engine)
             : base(engine, "Number", null, null, false)
             : base(engine, "Number", null, null, false)
         {
         {
@@ -38,6 +42,75 @@ namespace Jint.Native.Number
             SetOwnProperty("NEGATIVE_INFINITY", new PropertyDescriptor(double.NegativeInfinity, PropertyFlag.AllForbidden));
             SetOwnProperty("NEGATIVE_INFINITY", new PropertyDescriptor(double.NegativeInfinity, PropertyFlag.AllForbidden));
             SetOwnProperty("POSITIVE_INFINITY", new PropertyDescriptor(double.PositiveInfinity, PropertyFlag.AllForbidden));
             SetOwnProperty("POSITIVE_INFINITY", new PropertyDescriptor(double.PositiveInfinity, PropertyFlag.AllForbidden));
             SetOwnProperty("EPSILON", new PropertyDescriptor(JsNumber.JavaScriptEpsilon, PropertyFlag.AllForbidden));
             SetOwnProperty("EPSILON", new PropertyDescriptor(JsNumber.JavaScriptEpsilon, PropertyFlag.AllForbidden));
+            SetOwnProperty("MIN_SAFE_INTEGER", new PropertyDescriptor(MinSafeInteger, PropertyFlag.AllForbidden));
+            SetOwnProperty("MAX_SAFE_INTEGER", new PropertyDescriptor(MaxSafeInteger, PropertyFlag.AllForbidden));
+
+            FastAddProperty("isFinite", new ClrFunctionInstance(Engine, "isFinite", IsFinite, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("isInteger", new ClrFunctionInstance(Engine, "isInteger", IsInteger, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("isNaN", new ClrFunctionInstance(Engine, "isNaN", IsNaN, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("isSafeInteger", new ClrFunctionInstance(Engine, "isSafeInteger", IsSafeInteger, 1, PropertyFlag.Configurable), true, false, true);
+
+            FastAddProperty("parseFloat", new ClrFunctionInstance(Engine, "parseFloat", _engine.Global.ParseFloat, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("parseInt", new ClrFunctionInstance(Engine, "parseInt", _engine.Global.ParseInt, 0, PropertyFlag.Configurable), true, false, true);
+        }
+
+        private JsValue IsFinite(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            return double.IsInfinity(num._value) || double.IsNaN(num._value) ? JsBoolean.False : JsBoolean.True;
+        }
+
+        private JsValue IsInteger(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            if (double.IsInfinity(num._value) || double.IsNaN(num._value))
+            {
+                return JsBoolean.False;
+            }
+
+            var integer = TypeConverter.ToInteger(num);
+
+            return integer == num._value;
+        }
+
+        private JsValue IsNaN(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            return double.IsNaN(num._value);
+        }
+
+        private JsValue IsSafeInteger(JsValue thisObj, JsValue[] arguments)
+        {
+            if (!(arguments.At(0) is JsNumber num))
+            {
+                return false;
+            }
+
+            if (double.IsInfinity(num._value) || double.IsNaN(num._value))
+            {
+                return JsBoolean.False;
+            }
+
+            var integer = TypeConverter.ToInteger(num);
+
+            if (integer != num._value)
+            {
+                return false;
+            }
+
+            return System.Math.Abs(integer) <= MaxSafeInteger;
         }
         }
 
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)

+ 243 - 74
Jint/Native/Number/NumberPrototype.cs

@@ -1,9 +1,10 @@
-using System;
+using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
 using System.Text;
 using System.Text;
 using Jint.Native.Number.Dtoa;
 using Jint.Native.Number.Dtoa;
 using Jint.Pooling;
 using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime;
+using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
 
 
 namespace Jint.Native.Number
 namespace Jint.Native.Number
@@ -13,8 +14,6 @@ namespace Jint.Native.Number
     /// </summary>
     /// </summary>
     public sealed class NumberPrototype : NumberInstance
     public sealed class NumberPrototype : NumberInstance
     {
     {
-        private static readonly char[] _numberSeparators = {'.', 'e'};
-
         private NumberPrototype(Engine engine)
         private NumberPrototype(Engine engine)
             : base(engine)
             : base(engine)
         {
         {
@@ -36,12 +35,12 @@ namespace Jint.Native.Number
 
 
         public void Configure()
         public void Configure()
         {
         {
-            FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToNumberString), true, false, true);
-            FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString), true, false, true);
-            FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf), true, false, true);
-            FastAddProperty("toFixed", new ClrFunctionInstance(Engine, "toFixed", ToFixed, 1), true, false, true);
-            FastAddProperty("toExponential", new ClrFunctionInstance(Engine, "toExponential", ToExponential), true, false, true);
-            FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, "toPrecision", ToPrecision), true, false, true);
+            FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToNumberString, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toFixed", new ClrFunctionInstance(Engine, "toFixed", ToFixed, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toExponential", new ClrFunctionInstance(Engine, "toExponential", ToExponential, 1, PropertyFlag.Configurable), true, false, true);
+            FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, "toPrecision", ToPrecision, 1, PropertyFlag.Configurable), true, false, true);
         }
         }
 
 
         private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
         private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
@@ -101,9 +100,9 @@ namespace Jint.Native.Number
         private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
         private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
         {
         {
             var f = (int)TypeConverter.ToInteger(arguments.At(0, 0));
             var f = (int)TypeConverter.ToInteger(arguments.At(0, 0));
-            if (f < 0 || f > 20)
+            if (f < 0 || f > 100)
             {
             {
-                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 20");
+                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 100");
             }
             }
 
 
             var x = TypeConverter.ToNumber(thisObj);
             var x = TypeConverter.ToNumber(thisObj);
@@ -118,58 +117,213 @@ namespace Jint.Native.Number
                 return ToNumberString(x);
                 return ToNumberString(x);
             }
             }
 
 
+            // handle non-decimal with greater precision
+            if (System.Math.Abs(x - (long) x) < JsNumber.DoubleIsIntegerTolerance)
+            {
+                return ((long) x).ToString("f" + f, CultureInfo.InvariantCulture);
+            }
+
             return x.ToString("f" + f, CultureInfo.InvariantCulture);
             return x.ToString("f" + f, CultureInfo.InvariantCulture);
         }
         }
 
 
+        /// <summary>
+        /// https://www.ecma-international.org/ecma-262/6.0/#sec-number.prototype.toexponential
+        /// </summary>
         private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
         private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
         {
         {
-            var f = (int)TypeConverter.ToInteger(arguments.At(0, 16));
-            if (f < 0 || f > 20)
+            if (!thisObj.IsNumber() && ReferenceEquals(thisObj.TryCast<NumberInstance>(), null))
             {
             {
-                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 20");
+                ExceptionHelper.ThrowTypeError(Engine);
             }
             }
 
 
             var x = TypeConverter.ToNumber(thisObj);
             var x = TypeConverter.ToNumber(thisObj);
+            var fractionDigits = arguments.At(0);
+            if (fractionDigits.IsUndefined())
+            {
+                fractionDigits = JsNumber.PositiveZero;
+            }
+
+            var f = (int) TypeConverter.ToInteger(fractionDigits);
 
 
             if (double.IsNaN(x))
             if (double.IsNaN(x))
             {
             {
                 return "NaN";
                 return "NaN";
             }
             }
 
 
-            string format = string.Concat("#.", new string('0', f), "e+0");
-            return x.ToString(format, CultureInfo.InvariantCulture);
+            if (double.IsInfinity(x))
+            {
+                return thisObj.ToString();
+            }
+
+            if (f < 0 || f > 100)
+            {
+                ExceptionHelper.ThrowRangeError(_engine, "fractionDigits argument must be between 0 and 100");
+            }
+
+            if (arguments.At(0).IsUndefined())
+            {
+                f = -1;
+            }
+
+            bool negative = false;
+            if (x < 0)
+            {
+                x = -x;
+                negative = true;
+            }
+
+            int decimalPoint;
+            var dtoaBuilder = new DtoaBuilder();
+            if (f == -1)
+            {
+                DtoaNumberFormatter.DoubleToAscii(
+                    dtoaBuilder,
+                    x,
+                    DtoaMode.Shortest,
+                    requested_digits: 0,
+                    out _,
+                    out decimalPoint);
+                f = dtoaBuilder.Length - 1;
+            }
+            else
+            {
+                DtoaNumberFormatter.DoubleToAscii(
+                    dtoaBuilder,
+                    x,
+                    DtoaMode.Precision,
+                    requested_digits: f + 1,
+                    out _,
+                    out decimalPoint);
+            }
+
+            Debug.Assert(dtoaBuilder.Length > 0);
+            Debug.Assert(dtoaBuilder.Length <= f + 1);
+
+            int exponent = decimalPoint - 1;
+            var result = CreateExponentialRepresentation(dtoaBuilder, exponent, negative, f+1);
+            return result;
         }
         }
 
 
         private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)
         private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)
         {
         {
+            if (!thisObj.IsNumber() && ReferenceEquals(thisObj.TryCast<NumberInstance>(), null))
+            {
+                ExceptionHelper.ThrowTypeError(Engine);
+            }
+
             var x = TypeConverter.ToNumber(thisObj);
             var x = TypeConverter.ToNumber(thisObj);
+            var precisionArgument = arguments.At(0);
 
 
-            if (arguments.At(0).IsUndefined())
+            if (precisionArgument.IsUndefined())
             {
             {
                 return TypeConverter.ToString(x);
                 return TypeConverter.ToString(x);
             }
             }
 
 
-            var p = TypeConverter.ToInteger(arguments.At(0));
+            var p = (int) TypeConverter.ToInteger(precisionArgument);
 
 
-            if (double.IsInfinity(x) || double.IsNaN(x))
+            if (double.IsNaN(x))
             {
             {
-                return TypeConverter.ToString(x);
+                return "NaN";
             }
             }
 
 
-            if (p < 1 || p > 21)
+            if (double.IsInfinity(x))
             {
             {
-                ExceptionHelper.ThrowRangeError(_engine, "precision must be between 1 and 21");
+                return thisObj.ToString();
             }
             }
 
 
-            // Get the number of decimals
-            string str = x.ToString("e23", CultureInfo.InvariantCulture);
-            int decimals = str.IndexOfAny(_numberSeparators);
-            decimals = decimals == -1 ? str.Length : decimals;
+            if (p < 1 || p > 100)
+            {
+                ExceptionHelper.ThrowRangeError(_engine, "precision must be between 1 and 100");
+            }
 
 
-            p -= decimals;
-            p = p < 1 ? 1 : p;
+            var dtoaBuilder = new DtoaBuilder();
+            DtoaNumberFormatter.DoubleToAscii(
+                dtoaBuilder,
+                x,
+                DtoaMode.Precision,
+                p,
+                out var negative,
+                out var decimalPoint);
 
 
-            return x.ToString("f" + p, CultureInfo.InvariantCulture);
+
+            int exponent = decimalPoint - 1;
+            if (exponent < -6 || exponent >= p)
+            {
+                return CreateExponentialRepresentation(dtoaBuilder, exponent, negative, p);
+            }
+
+            using (var builder = StringBuilderPool.Rent())
+            {
+                // Use fixed notation.
+                if (negative)
+                {
+                    builder.Builder.Append('-');
+                }
+
+                if (decimalPoint <= 0)
+                {
+                    builder.Builder.Append("0.");
+                    builder.Builder.Append('0', -decimalPoint);
+                    builder.Builder.Append(dtoaBuilder._chars, 0, dtoaBuilder.Length);
+                    builder.Builder.Append('0', p - dtoaBuilder.Length);
+                }
+                else
+                {
+                    int m = System.Math.Min(dtoaBuilder.Length, decimalPoint);
+                    builder.Builder.Append(dtoaBuilder._chars, 0, m);
+                    builder.Builder.Append('0', System.Math.Max(0, decimalPoint - dtoaBuilder.Length));
+                    if (decimalPoint < p)
+                    {
+                        builder.Builder.Append('.');
+                        var extra = negative ? 2 : 1;
+                        if (dtoaBuilder.Length > decimalPoint)
+                        {
+                            int len = dtoaBuilder.Length - decimalPoint;
+                            int n = System.Math.Min(len, p - (builder.Builder.Length - extra));
+                            builder.Builder.Append(dtoaBuilder._chars, decimalPoint, n);
+                        }
+
+                        builder.Builder.Append('0', System.Math.Max(0, extra + (p - builder.Builder.Length)));
+                    }
+                }
+
+                return builder.ToString();
+            }
+        }
+
+        private string CreateExponentialRepresentation(
+            DtoaBuilder buffer,
+            int exponent,
+            bool negative,
+            int significantDigits)
+        {
+            bool negativeExponent = false;
+            if (exponent < 0)
+            {
+                negativeExponent = true;
+                exponent = -exponent;
+            }
+
+            using (var builder = StringBuilderPool.Rent())
+            {
+                if (negative)
+                {
+                    builder.Builder.Append('-');
+                }
+                builder.Builder.Append(buffer._chars[0]);
+                if (significantDigits != 1)
+                {
+                    builder.Builder.Append('.');
+                    builder.Builder.Append(buffer._chars, 1, buffer.Length - 1);
+                    int length = buffer.Length;
+                    builder.Builder.Append('0', significantDigits - length);
+                }
+
+                builder.Builder.Append('e');
+                builder.Builder.Append(negativeExponent ? '-' : '+');
+                builder.Builder.Append(exponent);
+                return builder.ToString();
+            }
         }
         }
 
 
         private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments)
         private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments)
@@ -227,7 +381,7 @@ namespace Jint.Native.Number
             return result;
             return result;
         }
         }
 
 
-        public static string ToBase(long n, int radix)
+        public string ToBase(long n, int radix)
         {
         {
             const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
             const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
             if (n == 0)
             if (n == 0)
@@ -235,7 +389,7 @@ namespace Jint.Native.Number
                 return "0";
                 return "0";
             }
             }
 
 
-            using (var result = StringBuilderPool.GetInstance())
+            using (var result = StringBuilderPool.Rent())
             {
             {
                 while (n > 0)
                 while (n > 0)
                 {
                 {
@@ -248,7 +402,7 @@ namespace Jint.Native.Number
             }
             }
         }
         }
 
 
-        public static string ToFractionBase(double n, int radix)
+        public string ToFractionBase(double n, int radix)
         {
         {
             // based on the repeated multiplication method
             // based on the repeated multiplication method
             // http://www.mathpath.org/concepts/Num/frac.htm
             // http://www.mathpath.org/concepts/Num/frac.htm
@@ -259,7 +413,7 @@ namespace Jint.Native.Number
                 return "0";
                 return "0";
             }
             }
 
 
-            using (var result = StringBuilderPool.GetInstance())
+            using (var result = StringBuilderPool.Rent())
             {
             {
                 while (n > 0 && result.Length < 50) // arbitrary limit
                 while (n > 0 && result.Length < 50) // arbitrary limit
                 {
                 {
@@ -274,7 +428,18 @@ namespace Jint.Native.Number
             }
             }
         }
         }
 
 
-        public static string ToNumberString(double m)
+        private string ToNumberString(double m)
+        {
+            using (var stringBuilder = StringBuilderPool.Rent())
+            {
+                return NumberToString(m, new DtoaBuilder(), stringBuilder.Builder);
+            }
+        }
+
+        internal static string NumberToString(
+            double m,
+            DtoaBuilder builder,
+            StringBuilder stringBuilder)
         {
         {
             if (double.IsNaN(m))
             if (double.IsNaN(m))
             {
             {
@@ -286,67 +451,71 @@ namespace Jint.Native.Number
                 return "0";
                 return "0";
             }
             }
 
 
-            if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
+            if (double.IsPositiveInfinity(m))
             {
             {
                 return "Infinity";
                 return "Infinity";
             }
             }
 
 
-            if (m < 0)
+            if (double.IsNegativeInfinity(m))
             {
             {
-                return "-" + ToNumberString(-m);
+                return "-Infinity";
             }
             }
 
 
-            // V8 FastDtoa can't convert all numbers, so try it first but
-            // fall back to old DToA in case it fails
-            var result = FastDtoa.NumberToString(m);
-            if (result != null)
-            {
-                return result;
-            }
+            DtoaNumberFormatter.DoubleToAscii(
+                builder,
+                m,
+                DtoaMode.Shortest,
+                0,
+                out var negative,
+                out var decimal_point);
 
 
-            // s is all digits (significand)
-            // k number of digits of s
-            // n total of digits in fraction s*10^n-k=m
-            // 123.4 s=1234, k=4, n=3
-            // 1234000 s = 1234, k=4, n=7
-            string s = null;
-            var rFormat = m.ToString("r", CultureInfo.InvariantCulture);
-            if (rFormat.IndexOf("e", StringComparison.OrdinalIgnoreCase) == -1)
+            if (negative)
             {
             {
-                s = rFormat.Replace(".", "").TrimStart('0').TrimEnd('0');
+                stringBuilder.Append('-');
             }
             }
 
 
-            const string format = "0.00000000000000000e0";
-            var parts = m.ToString(format, CultureInfo.InvariantCulture).Split('e');
-            if (s == null)
+            if (builder.Length <= decimal_point && decimal_point <= 21)
             {
             {
-                s = parts[0].TrimEnd('0').Replace(".", "");
+                // ECMA-262 section 9.8.1 step 6.
+                stringBuilder.Append(builder._chars, 0, builder.Length);
+                stringBuilder.Append('0', decimal_point - builder.Length);
             }
             }
-
-            var n = int.Parse(parts[1]) + 1;
-            var k = s.Length;
-
-            if (k <= n && n <= 21)
+            else if (0 < decimal_point && decimal_point <= 21)
             {
             {
-                return s + new string('0', n - k);
+                // ECMA-262 section 9.8.1 step 7.
+                stringBuilder.Append(builder._chars, 0, decimal_point);
+                stringBuilder.Append('.');
+                stringBuilder.Append(builder._chars, decimal_point, builder.Length - decimal_point);
             }
             }
-
-            if (0 < n && n <= 21)
+            else if (decimal_point <= 0 && decimal_point > -6)
             {
             {
-                return s.Substring(0, n) + '.' + s.Substring(n);
+                // ECMA-262 section 9.8.1 step 8.
+                stringBuilder.Append("0.");
+                stringBuilder.Append('0', -decimal_point);
+                stringBuilder.Append(builder._chars, 0, builder.Length);
             }
             }
-
-            if (-6 < n && n <= 0)
+            else
             {
             {
-                return "0." + new string('0', -n) + s;
-            }
+                // ECMA-262 section 9.8.1 step 9 and 10 combined.
+                stringBuilder.Append(builder._chars[0]);
+                if (builder.Length != 1)
+                {
+                    stringBuilder.Append('.');
+                    stringBuilder.Append(builder._chars, 1, builder.Length - 1);
+                }
 
 
-            if (k == 1)
-            {
-                return s + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
+                stringBuilder.Append('e');
+                stringBuilder.Append((decimal_point >= 0) ? '+' : '-');
+                int exponent = decimal_point - 1;
+                if (exponent < 0)
+                {
+                    exponent = -exponent;
+                }
+
+                stringBuilder.Append(exponent);
             }
             }
 
 
-            return s.Substring(0, 1) + "." + s.Substring(1) + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
+            return stringBuilder.ToString();
         }
         }
     }
     }
 }
 }

+ 1 - 1
Jint/Native/Object/ObjectInstance.cs

@@ -26,7 +26,7 @@ namespace Jint.Native.Object
         internal StringDictionarySlim<PropertyDescriptor> _properties;
         internal StringDictionarySlim<PropertyDescriptor> _properties;
 
 
         private readonly string _class;
         private readonly string _class;
-        protected readonly Engine _engine;
+        protected internal readonly Engine _engine;
 
 
         public ObjectInstance(Engine engine) : this(engine, "Object")
         public ObjectInstance(Engine engine) : this(engine, "Object")
         {
         {

+ 1 - 2
Jint/Native/String/StringConstructor.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Text;
 using Jint.Native.Array;
 using Jint.Native.Array;
 using Jint.Native.Function;
 using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.Object;
@@ -108,7 +107,7 @@ namespace Jint.Native.String
                 return JsString.Empty;
                 return JsString.Empty;
             }
             }
 
 
-            using (var result = StringBuilderPool.GetInstance())
+            using (var result = StringBuilderPool.Rent())
             {
             {
                 for (var i = 0; i < length; i++)
                 for (var i = 0; i < length; i++)
                 {
                 {

+ 4 - 4
Jint/Native/String/StringPrototype.cs

@@ -263,7 +263,7 @@ namespace Jint.Native.String
             return s.Substring(from, length);
             return s.Substring(from, length);
         }
         }
 
 
-        private static JsValue Substr(JsValue thisObj, JsValue[] arguments)
+        private JsValue Substr(JsValue thisObj, JsValue[] arguments)
         {
         {
             var s = TypeConverter.ToString(thisObj);
             var s = TypeConverter.ToString(thisObj);
             var start = TypeConverter.ToInteger(arguments.At(0));
             var start = TypeConverter.ToInteger(arguments.At(0));
@@ -521,7 +521,7 @@ namespace Jint.Native.String
                     // $`	Inserts the portion of the string that precedes the matched substring.
                     // $`	Inserts the portion of the string that precedes the matched substring.
                     // $'	Inserts the portion of the string that follows the matched substring.
                     // $'	Inserts the portion of the string that follows the matched substring.
                     // $n or $nn	Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
                     // $n or $nn	Where n or nn are decimal digits, inserts the nth parenthesized submatch string, provided the first argument was a RegExp object.
-                    using (var replacementBuilder = StringBuilderPool.GetInstance())
+                    using (var replacementBuilder = StringBuilderPool.Rent())
                     {
                     {
                         for (int i = 0; i < replaceString.Length; i++)
                         for (int i = 0; i < replaceString.Length; i++)
                         {
                         {
@@ -641,7 +641,7 @@ namespace Jint.Native.String
                 _engine._jsValueArrayPool.ReturnArray(args);
                 _engine._jsValueArrayPool.ReturnArray(args);
 
 
                 // Replace only the first match.
                 // Replace only the first match.
-                using (var result = StringBuilderPool.GetInstance())
+                using (var result = StringBuilderPool.Rent())
                 {
                 {
                     result.Builder.EnsureCapacity(thisString.Length + (substr.Length - substr.Length));
                     result.Builder.EnsureCapacity(thisString.Length + (substr.Length - substr.Length));
                     result.Builder.Append(thisString, 0, start);
                     result.Builder.Append(thisString, 0, start);
@@ -1130,7 +1130,7 @@ namespace Jint.Native.String
                 return new string(str[0], n);
                 return new string(str[0], n);
             }
             }
 
 
-            using (var sb = StringBuilderPool.GetInstance())
+            using (var sb = StringBuilderPool.Rent())
             {
             {
                 sb.Builder.EnsureCapacity(n * str.Length);
                 sb.Builder.EnsureCapacity(n * str.Length);
                 for (var i = 0; i < n; ++i)
                 for (var i = 0; i < n; ++i)

+ 278 - 0
Jint/Pooling/ConcurrentObjectPool.cs

@@ -0,0 +1,278 @@
+// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
+
+// define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will
+// make everything about 2-3x slower
+//
+// #define TRACE_LEAKS
+
+// define DETECT_LEAKS to detect possible leaks
+// #if DEBUG
+// #define DETECT_LEAKS  //for now always enable DETECT_LEAKS in debug.
+// #endif
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+#if DETECT_LEAKS
+using System.Runtime.CompilerServices;
+#endif
+
+namespace Jint.Pooling
+{
+    /// <summary>
+    /// Generic implementation of object pooling pattern with predefined pool size limit. The main
+    /// purpose is that limited number of frequently used objects can be kept in the pool for
+    /// further recycling.
+    ///
+    /// Notes:
+    /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
+    ///    is no space in the pool, extra returned objects will be dropped.
+    ///
+    /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
+    ///    a relatively short time. Keeping checked out objects for long durations is ok, but
+    ///    reduces usefulness of pooling. Just new up your own.
+    ///
+    /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
+    /// Rationale:
+    ///    If there is no intent for reusing the object, do not use pool - just use "new".
+    /// </summary>
+    internal class ConcurrentObjectPool<T> where T : class
+    {
+        [DebuggerDisplay("{Value,nq}")]
+        private struct Element
+        {
+            internal T Value;
+        }
+
+        /// <remarks>
+        /// Not using System.Func{T} because this file is linked into the (debugger) Formatter,
+        /// which does not have that type (since it compiles against .NET 2.0).
+        /// </remarks>
+        internal delegate T Factory();
+
+        // Storage for the pool objects. The first item is stored in a dedicated field because we
+        // expect to be able to satisfy most requests from it.
+        private T _firstItem;
+        private readonly Element[] _items;
+
+        // factory is stored for the lifetime of the pool. We will call this only when pool needs to
+        // expand. compared to "new T()", Func gives more flexibility to implementers and faster
+        // than "new T()".
+        private readonly Factory _factory;
+
+#if DETECT_LEAKS
+        private static readonly ConditionalWeakTable<T, LeakTracker> leakTrackers = new ConditionalWeakTable<T, LeakTracker>();
+
+        private class LeakTracker : IDisposable
+        {
+            private volatile bool disposed;
+
+#if TRACE_LEAKS
+            internal volatile object Trace = null;
+#endif
+
+            public void Dispose()
+            {
+                disposed = true;
+                GC.SuppressFinalize(this);
+            }
+
+            private string GetTrace()
+            {
+#if TRACE_LEAKS
+                return Trace == null ? "" : Trace.ToString();
+#else
+                return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n";
+#endif
+            }
+
+            ~LeakTracker()
+            {
+                if (!this.disposed && !Environment.HasShutdownStarted)
+                {
+                    var trace = GetTrace();
+
+                    // If you are seeing this message it means that object has been allocated from the pool
+                    // and has not been returned back. This is not critical, but turns pool into rather
+                    // inefficient kind of "new".
+                    Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nPool detected potential leaking of {typeof(T)}. \n Location of the leak: \n {GetTrace()} TRACEOBJECTPOOLLEAKS_END");
+                }
+            }
+        }
+#endif
+
+        internal ConcurrentObjectPool(Factory factory)
+            : this(factory, Environment.ProcessorCount * 2)
+        { }
+
+        internal ConcurrentObjectPool(Factory factory, int size)
+        {
+            Debug.Assert(size >= 1);
+            _factory = factory;
+            _items = new Element[size - 1];
+        }
+
+        private T CreateInstance()
+        {
+            var inst = _factory();
+            return inst;
+        }
+
+        /// <summary>
+        /// Produces an instance.
+        /// </summary>
+        /// <remarks>
+        /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+        /// Note that Free will try to store recycled objects close to the start thus statistically
+        /// reducing how far we will typically search.
+        /// </remarks>
+        internal T Allocate()
+        {
+            // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
+            // Note that the initial read is optimistically not synchronized. That is intentional.
+            // We will interlock only when we have a candidate. in a worst case we may miss some
+            // recently returned objects. Not a big deal.
+            T inst = _firstItem;
+            if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
+            {
+                inst = AllocateSlow();
+            }
+
+#if DETECT_LEAKS
+            var tracker = new LeakTracker();
+            leakTrackers.Add(inst, tracker);
+
+#if TRACE_LEAKS
+            var frame = CaptureStackTrace();
+            tracker.Trace = frame;
+#endif
+#endif
+            return inst;
+        }
+
+        private T AllocateSlow()
+        {
+            var items = _items;
+
+            for (int i = 0; i < items.Length; i++)
+            {
+                // Note that the initial read is optimistically not synchronized. That is intentional.
+                // We will interlock only when we have a candidate. in a worst case we may miss some
+                // recently returned objects. Not a big deal.
+                T inst = items[i].Value;
+                if (inst != null)
+                {
+                    if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
+                    {
+                        return inst;
+                    }
+                }
+            }
+
+            return CreateInstance();
+        }
+
+        /// <summary>
+        /// Returns objects to the pool.
+        /// </summary>
+        /// <remarks>
+        /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
+        /// Note that Free will try to store recycled objects close to the start thus statistically
+        /// reducing how far we will typically search in Allocate.
+        /// </remarks>
+        internal void Free(T obj)
+        {
+            Validate(obj);
+            ForgetTrackedObject(obj);
+
+            if (_firstItem == null)
+            {
+                // Intentionally not using interlocked here.
+                // In a worst case scenario two objects may be stored into same slot.
+                // It is very unlikely to happen and will only mean that one of the objects will get collected.
+                _firstItem = obj;
+            }
+            else
+            {
+                FreeSlow(obj);
+            }
+        }
+
+        private void FreeSlow(T obj)
+        {
+            var items = _items;
+            for (int i = 0; i < items.Length; i++)
+            {
+                if (items[i].Value == null)
+                {
+                    // Intentionally not using interlocked here.
+                    // In a worst case scenario two objects may be stored into same slot.
+                    // It is very unlikely to happen and will only mean that one of the objects will get collected.
+                    items[i].Value = obj;
+                    break;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Removes an object from leak tracking.
+        ///
+        /// This is called when an object is returned to the pool.  It may also be explicitly
+        /// called if an object allocated from the pool is intentionally not being returned
+        /// to the pool.  This can be of use with pooled arrays if the consumer wants to
+        /// return a larger array to the pool than was originally allocated.
+        /// </summary>
+        [Conditional("DEBUG")]
+        internal void ForgetTrackedObject(T old, T replacement = null)
+        {
+#if DETECT_LEAKS
+            LeakTracker tracker;
+            if (leakTrackers.TryGetValue(old, out tracker))
+            {
+                tracker.Dispose();
+                leakTrackers.Remove(old);
+            }
+            else
+            {
+                var trace = CaptureStackTrace();
+                Debug.WriteLine($"TRACEOBJECTPOOLLEAKS_BEGIN\nObject of type {typeof(T)} was freed, but was not from pool. \n Callstack: \n {trace} TRACEOBJECTPOOLLEAKS_END");
+            }
+
+            if (replacement != null)
+            {
+                tracker = new LeakTracker();
+                leakTrackers.Add(replacement, tracker);
+            }
+#endif
+        }
+
+#if DETECT_LEAKS
+        private static Lazy<Type> _stackTraceType = new Lazy<Type>(() => Type.GetType("System.Diagnostics.StackTrace"));
+
+        private static object CaptureStackTrace()
+        {
+            return Activator.CreateInstance(_stackTraceType.Value);
+        }
+#endif
+
+        [Conditional("DEBUG")]
+        private void Validate(object obj)
+        {
+            Debug.Assert(obj != null, "freeing null?");
+
+            Debug.Assert(_firstItem != obj, "freeing twice?");
+
+            var items = _items;
+            for (int i = 0; i < items.Length; i++)
+            {
+                var value = items[i].Value;
+                if (value == null)
+                {
+                    return;
+                }
+
+                Debug.Assert(value != obj, "freeing twice?");
+            }
+        }
+    }}

+ 34 - 45
Jint/Pooling/StringBuilderPool.cs

@@ -7,67 +7,56 @@ using System.Text;
 namespace Jint.Pooling
 namespace Jint.Pooling
 {
 {
     /// <summary>
     /// <summary>
-    /// The usage is:
-    ///        var inst = PooledStringBuilder.GetInstance();
-    ///        var sb = inst.builder;
-    ///        ... Do Stuff...
-    ///        ... sb.ToString() ...
-    ///        inst.Free();
+    /// Pooling of StringBuilder instances.
     /// </summary>
     /// </summary>
-    internal sealed class StringBuilderPool : IDisposable
+    internal sealed class StringBuilderPool
     {
     {
-        // global pool
-        private static readonly ObjectPool<StringBuilderPool> s_poolInstance = CreatePool();
+        private static readonly ConcurrentObjectPool<StringBuilder> _pool;
 
 
-        public readonly StringBuilder Builder = new StringBuilder();
-        private readonly ObjectPool<StringBuilderPool> _pool;
-
-        private StringBuilderPool(ObjectPool<StringBuilderPool> pool)
+        static StringBuilderPool()
         {
         {
-            Debug.Assert(pool != null);
-            _pool = pool;
+            _pool = new ConcurrentObjectPool<StringBuilder>(() => new StringBuilder());
         }
         }
 
 
-        public int Length => Builder.Length;
-
-        // if someone needs to create a private pool;
-        /// <summary>
-        /// If someone need to create a private pool
-        /// </summary>
-        /// <param name="size">The size of the pool.</param>
-        /// <returns></returns>
-        internal static ObjectPool<StringBuilderPool> CreatePool(int size = 32)
+        public static BuilderWrapper Rent()
         {
         {
-            ObjectPool<StringBuilderPool> pool = null;
-            pool = new ObjectPool<StringBuilderPool>(() => new StringBuilderPool(pool), size);
-            return pool;
+            var builder = _pool.Allocate();
+            Debug.Assert(builder.Length == 0);
+            return new BuilderWrapper(builder, _pool);
         }
         }
 
 
-        public static StringBuilderPool GetInstance()
+        internal readonly struct BuilderWrapper : IDisposable
         {
         {
-            var builder = s_poolInstance.Allocate();
-            Debug.Assert(builder.Builder.Length == 0);
-            return builder;
-        }
+            public readonly StringBuilder Builder;
+            private readonly ConcurrentObjectPool<StringBuilder> _pool;
 
 
-        public override string ToString()
-        {
-            return Builder.ToString();
-        }
+            public BuilderWrapper(StringBuilder builder, ConcurrentObjectPool<StringBuilder> pool)
+            {
+                Builder = builder;
+                _pool = pool;
+            }
 
 
-        public void Dispose()
-        {
-            var builder = Builder;
+            public int Length => Builder.Length;
 
 
-            // do not store builders that are too large.
-            if (builder.Capacity <= 1024)
+            public override string ToString()
             {
             {
-                builder.Clear();
-                _pool.Free(this);
+                return Builder.ToString();
             }
             }
-            else
+
+            public void Dispose()
             {
             {
-                _pool.ForgetTrackedObject(this);
+                var builder = Builder;
+
+                // do not store builders that are too large.
+                if (builder.Capacity <= 1024)
+                {
+                    builder.Clear();
+                    _pool.Free(builder);
+                }
+                else
+                {
+                    _pool.ForgetTrackedObject(builder);
+                }
             }
             }
         }
         }
     }
     }

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -230,7 +230,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             return false;
             return false;
         }
         }
 
 
-        public static bool SameValue(JsValue x, JsValue y)
+        protected internal static bool SameValue(JsValue x, JsValue y)
         {
         {
             var typea = TypeConverter.GetPrimitiveType(x);
             var typea = TypeConverter.GetPrimitiveType(x);
             var typeb = TypeConverter.GetPrimitiveType(y);
             var typeb = TypeConverter.GetPrimitiveType(y);
@@ -274,7 +274,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
             }
         }
         }
 
 
-        public static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true)
+        protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true)
         {
         {
             JsValue px, py;
             JsValue px, py;
             if (leftFirst)
             if (leftFirst)

+ 1 - 2
Jint/Runtime/Interpreter/Expressions/JintTemplateLiteralExpression.cs

@@ -1,4 +1,3 @@
-using System.Text;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
 using Jint.Pooling;
 using Jint.Pooling;
@@ -35,7 +34,7 @@ namespace Jint.Runtime.Interpreter.Expressions
 
 
         private JsString BuildString()
         private JsString BuildString()
         {
         {
-            using (var sb = StringBuilderPool.GetInstance())
+            using (var sb = StringBuilderPool.Rent())
             {
             {
                 for (var i = 0; i < _templateLiteralExpression.Quasis.Count; i++)
                 for (var i = 0; i < _templateLiteralExpression.Quasis.Count; i++)
                 {
                 {

+ 1 - 2
Jint/Runtime/JavaScriptException.cs

@@ -1,5 +1,4 @@
 using System;
 using System;
-using System.Text;
 using Esprima;
 using Esprima;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
@@ -38,7 +37,7 @@ namespace Jint.Runtime
         public JavaScriptException SetCallstack(Engine engine, Location location = null)
         public JavaScriptException SetCallstack(Engine engine, Location location = null)
         {
         {
             Location = location;
             Location = location;
-            using (var sb = StringBuilderPool.GetInstance())
+            using (var sb = StringBuilderPool.Rent())
             {
             {
                 foreach (var cse in engine.CallStack)
                 foreach (var cse in engine.CallStack)
                 {
                 {

+ 46 - 14
Jint/Runtime/TypeConverter.cs

@@ -6,8 +6,10 @@ using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Number;
 using Jint.Native.Number;
+using Jint.Native.Number.Dtoa;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.String;
 using Jint.Native.String;
+using Jint.Pooling;
 using Jint.Runtime.References;
 using Jint.Runtime.References;
 
 
 namespace Jint.Runtime
 namespace Jint.Runtime
@@ -170,29 +172,46 @@ namespace Jint.Runtime
             // todo: use a common implementation with JavascriptParser
             // todo: use a common implementation with JavascriptParser
             try
             try
             {
             {
-                if (!s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
+                if (s.Length > 2 && s[0] == '0' && char.IsLetter(s[1]))
                 {
                 {
-                    var start = s[0];
-                    if (start != '+' && start != '-' && start != '.' && !char.IsDigit(start))
+                    int fromBase = 0;
+                    if (s[1] == 'x' || s[1] == 'X')
                     {
                     {
-                        return double.NaN;
+                        fromBase = 16;
                     }
                     }
 
 
-                    double n = double.Parse(s,
-                        NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign |
-                        NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite |
-                        NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
-                    if (s.StartsWith("-") && n == 0)
+                    if (s[1] == 'o' || s[1] == 'O')
                     {
                     {
-                        return -0.0;
+                        fromBase = 8;
                     }
                     }
 
 
-                    return n;
+                    if (s[1] == 'b' || s[1] == 'B')
+                    {
+                        fromBase = 2;
+                    }
+
+                    if (fromBase > 0)
+                    {
+                        return Convert.ToInt32(s.Substring(2), fromBase);
+                    }
                 }
                 }
 
 
-                int i = int.Parse(s.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
+                var start = s[0];
+                if (start != '+' && start != '-' && start != '.' && !char.IsDigit(start))
+                {
+                    return double.NaN;
+                }
+
+                double n = double.Parse(s,
+                    NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign |
+                    NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite |
+                    NumberStyles.AllowExponent, CultureInfo.InvariantCulture);
+                if (s.StartsWith("-") && n == 0)
+                {
+                    return -0.0;
+                }
 
 
-                return i;
+                return n;
             }
             }
             catch (OverflowException)
             catch (OverflowException)
             {
             {
@@ -306,6 +325,15 @@ namespace Jint.Runtime
                 : c.ToString();
                 : c.ToString();
         }
         }
 
 
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static string ToString(ulong i)
+        {
+            return i >= 0 && i < (ulong) intToString.Length
+                ? intToString[i]
+                : i.ToString();
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static string ToString(double d)
         internal static string ToString(double d)
         {
         {
@@ -315,7 +343,11 @@ namespace Jint.Runtime
                 return ToString((long) d);
                 return ToString((long) d);
             }
             }
 
 
-            return NumberPrototype.ToNumberString(d);
+            using (var stringBuilder = StringBuilderPool.Rent())
+            {
+                // we can create smaller array as we know the format to be short
+                return NumberPrototype.NumberToString(d, new DtoaBuilder(17), stringBuilder.Builder);
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 1
README.md

@@ -171,7 +171,7 @@ ES6 features which are being implemented:
 - [ ] [subclassable built-ins](https://github.com/lukehoban/es6features/blob/master/README.md#subclassable-built-ins)
 - [ ] [subclassable built-ins](https://github.com/lukehoban/es6features/blob/master/README.md#subclassable-built-ins)
 - [ ] [promises](https://github.com/lukehoban/es6features/blob/master/README.md#promises)
 - [ ] [promises](https://github.com/lukehoban/es6features/blob/master/README.md#promises)
 - [x] [math APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [x] [math APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
-- [ ] [number APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
+- [x] [number APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [x] [string APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [x] [string APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [x] [array APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [x] [array APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [ ] [object APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)
 - [ ] [object APIs](https://github.com/lukehoban/es6features/blob/master/README.md#math--number--string--array--object-apis)