Browse Source

Using FastDtoa to serialize numbers

Sebastien Ros 11 years ago
parent
commit
5f713ad215

+ 14 - 0
CREDITS.txt

@@ -7,3 +7,17 @@ Jint contains a C# port of the Esprima Javascript parser.
 Website:      http://esprima.org/
 Copyright:    Copyright (c) Ariya Hidayat
 License:      BSD - https://github.com/ariya/esprima/blob/master/LICENSE.BSD
+
+Google Inc
+-----
+Jint uses the FastDtoa algorithm implemented for Google V8
+
+Copyright:    Copyright 2010 the V8 project authors. All rights reserved.
+
+Mozilla
+-----
+Jint number serialization is based on the Java port of the FastDtoa algorithm from Mozilla
+
+Website:      http://mozilla.org/
+Copyright:    Copyright (c) Mozilla
+License:      MPL 2.0 - http://mozilla.org/MPL/2.0/

+ 46 - 62
Jint.Benchmark/Program.cs

@@ -1,20 +1,17 @@
 using System;
 using System.Diagnostics;
-using System.IO;
-using System.Net;
-using Jint.Runtime;
 
 namespace Jint.Benchmark
 {
     class Program
     {
-        private const string simple = @"
+        private const string Script = @"
             var o = {};
             o.Foo = 'bar';
-            o.Baz = 42;
+            o.Baz = 42.0001;
             o.Blah = o.Foo + o.Baz;
 
-            if(o.Blah != 'bar42') throw TypeError;
+            if(o.Blah != 'bar42.0001') throw TypeError;
 
             function fib(n){
                 if(n<2) { 
@@ -24,91 +21,78 @@ namespace Jint.Benchmark
                 return fib(n-1) + fib(n-2);  
             }
 
-            if(fib(3) != 8) throw TypeError;
+            if(fib(3) != 2) throw TypeError;
         ";
 
-        private static readonly string[] Sunspider = new string[]
-            {
-                "https://gist.github.com/sebastienros/6274930/raw/bf0ab41e788eb9b978a29180f5cb4d614bc00dc5/gistfile1.js"
-            };
-
-        static void Main(string[] args)
+        static void Main()
         {
             const bool runIronJs = true;
             const bool runJint = true;
             const bool runJurassic = true;
 
-            const int iterations = 1000;
+            const int iterations = 100;
             const bool reuseEngine = false;
 
             var watch = new Stopwatch();
 
-            foreach (var url in Sunspider)
-            {
-                var script = new WebClient().DownloadString(url);
 
-
-                if (runIronJs)
+            if (runIronJs)
+            {
+                IronJS.Hosting.CSharp.Context ironjs;
+                ironjs = new IronJS.Hosting.CSharp.Context();
+                ironjs.Execute(Script);
+                watch.Restart();
+                for (var i = 0; i < iterations; i++)
                 {
-                    IronJS.Hosting.CSharp.Context ironjs;
-                    ironjs = new IronJS.Hosting.CSharp.Context();
-                    ironjs.Execute(script);
-                    watch.Restart();
-                    for (var i = 0; i < iterations; i++)
+                    if (!reuseEngine)
                     {
-                        if (!reuseEngine)
-                        {
-                            ironjs = new IronJS.Hosting.CSharp.Context();
-                        }
-
-                        ironjs.Execute(script);
+                        ironjs = new IronJS.Hosting.CSharp.Context();
                     }
 
-                    Console.WriteLine("{2} | IronJs: {0} iterations in {1} ms", iterations, watch.ElapsedMilliseconds,
-                                      Path.GetFileName(url));
+                    ironjs.Execute(Script);
                 }
 
-                if (runJint)
-                {
-                    Engine jint;
-                    jint = new Engine();
-                    jint.Execute(script);
+                Console.WriteLine("IronJs: {0} iterations in {1} ms", iterations, watch.ElapsedMilliseconds);
+            }
 
-                    watch.Restart();
-                    for (var i = 0; i < iterations; i++)
-                    {
-                        if (!reuseEngine)
-                        {
-                            jint = new Jint.Engine();
-                        }
+            if (runJint)
+            {
+                Engine jint;
+                jint = new Engine();
+                jint.Execute(Script);
 
-                        jint.Execute(script);
+                watch.Restart();
+                for (var i = 0; i < iterations; i++)
+                {
+                    if (!reuseEngine)
+                    {
+                        jint = new Jint.Engine();
                     }
 
-                    Console.WriteLine("{2} | Jint: {0} iterations in {1} ms", iterations, watch.ElapsedMilliseconds,
-                                      Path.GetFileName(url));
+                    jint.Execute(Script);
                 }
 
-                if (runJurassic)
-                {
-                    Jurassic.ScriptEngine jurassic;
-                    jurassic = new Jurassic.ScriptEngine();
-                    jurassic.Execute(script);
+                Console.WriteLine("Jint: {0} iterations in {1} ms", iterations, watch.ElapsedMilliseconds);
+            }
 
-                    watch.Restart();
-                    for (var i = 0; i < iterations; i++)
-                    {
-                        if (!reuseEngine)
-                        {
-                            jurassic = new Jurassic.ScriptEngine();
-                        }
+            if (runJurassic)
+            {
+                Jurassic.ScriptEngine jurassic;
+                jurassic = new Jurassic.ScriptEngine();
+                jurassic.Execute(Script);
 
-                        jurassic.Execute(script);
+                watch.Restart();
+                for (var i = 0; i < iterations; i++)
+                {
+                    if (!reuseEngine)
+                    {
+                        jurassic = new Jurassic.ScriptEngine();
                     }
 
-                    Console.WriteLine("{2} | Jurassic: {0} iterations in {1} ms", iterations, watch.ElapsedMilliseconds,
-                                      Path.GetFileName(url));
+                    jurassic.Execute(Script);
                 }
+
+                Console.WriteLine("Jurassic: {0} iterations in {1} ms", iterations, watch.ElapsedMilliseconds);
             }
         }
     }

+ 3 - 0
Jint.Tests/Runtime/EngineTests.cs

@@ -600,7 +600,10 @@ namespace Jint.Tests.Runtime
                 assert(String(-14.915832707045631) === '-14.915832707045631');
                 assert(String(0.5) === '0.5');
                 assert(String(0.00000001) === '1e-8');
+                assert(String(0.000001) === '0.000001');
                 assert(String(-1.0) === '-1');
+                assert(String(30.0) === '30');
+                assert(String(0.2388906159889881) === '0.2388906159889881');
             ");
         }
 

+ 6 - 0
Jint/Jint.csproj

@@ -57,6 +57,12 @@
     <Compile Include="Native\IPrimitiveInstance.cs" />
     <Compile Include="Native\JsValue.cs" />
     <Compile Include="Native\Null.cs" />
+    <Compile Include="Native\Number\Dtoa\CachePowers.cs" />
+    <Compile Include="Native\Number\Dtoa\DiyFp.cs" />
+    <Compile Include="Native\Number\Dtoa\DoubleHelper.cs" />
+    <Compile Include="Native\Number\Dtoa\FastDtoa.cs" />
+    <Compile Include="Native\Number\Dtoa\FastDtoaBuilder.cs" />
+    <Compile Include="Native\Number\Dtoa\NumberExtensions.cs" />
     <Compile Include="Native\RegExp\RegExpConstructor.cs" />
     <Compile Include="Native\RegExp\RegExpInstance.cs" />
     <Compile Include="Native\RegExp\RegExpPrototype.cs" />

+ 165 - 0
Jint/Native/Number/Dtoa/CachePowers.cs

@@ -0,0 +1,165 @@
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Ported to Java from Mozilla's version of V8-dtoa by Hannes Wallnoefer.
+// The original revision was 67d1049b0bf9 from the mozilla-central tree.
+
+using System.Diagnostics;
+
+namespace Jint.Native.Number.Dtoa
+{
+
+    public class CachedPowers
+    {
+        private const double Kd1Log210 = 0.30102999566398114; //  1 / lg(10)
+
+        private class CachedPower
+        {
+            internal readonly long Significand;
+            internal readonly short BinaryExponent;
+            internal readonly short DecimalExponent;
+
+            internal CachedPower(ulong significand, short binaryExponent, short decimalExponent)
+            {
+                Significand = (long) significand;
+                BinaryExponent = binaryExponent;
+                DecimalExponent = decimalExponent;
+            }
+        }
+
+
+        internal static int GetCachedPower(int e, int alpha, int gamma, DiyFp cMk)
+        {
+            const int kQ = DiyFp.KSignificandSize;
+            double k = System.Math.Ceiling((alpha - e + kQ - 1)*Kd1Log210);
+            int index = (GrisuCacheOffset + (int) k - 1)/CachedPowersSpacing + 1;
+            CachedPower cachedPower = CACHED_POWERS[index];
+
+            cMk.F = cachedPower.Significand;
+            cMk.E = cachedPower.BinaryExponent;
+            Debug.Assert((alpha <= cMk.E + e) && (cMk.E + e <= gamma));
+            return cachedPower.DecimalExponent;
+        }
+
+        // Code below is converted from GRISU_CACHE_NAME(8) in file "powers-ten.h"
+        // Regexp to convert this from original C++ source:
+        // \{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 =
+        {
+            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)
+        };
+
+        private const int GrisuCacheOffset = 308;
+
+
+    }
+}

+ 150 - 0
Jint/Native/Number/Dtoa/DiyFp.cs

@@ -0,0 +1,150 @@
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Ported to Java from Mozilla's version of V8-dtoa by Hannes Wallnoefer.
+// The original revision was 67d1049b0bf9 from the mozilla-central tree.
+
+using System.Diagnostics;
+
+namespace Jint.Native.Number.Dtoa
+{
+
+// This "Do It Yourself Floating Point" class implements a floating-point number
+// with a uint64 significand and an int exponent. Normalized DiyFp numbers will
+// have the most significant bit of the significand set.
+// Multiplication and Subtraction do not normalize their results.
+// DiyFp are not designed to contain special doubles (NaN and Infinity).
+    internal class DiyFp
+    {
+        internal const int KSignificandSize = 64;
+        private const ulong KUint64MSB = 0x8000000000000000L;
+
+        internal DiyFp()
+        {
+            F = 0;
+            E = 0;
+        }
+
+        internal DiyFp(long f, int e)
+        {
+            F = f;
+            E = e;
+        }
+
+        public long F { get; set; }
+        public int E { get; set; }
+
+        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));
+        }
+
+        // this = this - other.
+        // The exponents of both numbers must be the same and the significand of this
+        // must be bigger than the significand of other.
+        // The result will not be normalized.
+        private void Subtract(DiyFp other)
+        {
+            Debug.Assert(E == other.E);
+            Debug.Assert(Uint64Gte(F, other.F));
+
+            F -= other.F;
+        }
+
+        // Returns a - b.
+        // The exponents of both numbers must be the same and this must be bigger
+        // than other. The result will not be normalized.
+        internal static DiyFp Minus(DiyFp a, DiyFp b)
+        {
+            DiyFp result = new DiyFp(a.F, a.E);
+            result.Subtract(b);
+            return result;
+        }
+
+        // this = this * other.
+        private void Multiply(DiyFp other)
+        {
+            // Simply "emulates" a 128 bit multiplication.
+            // However: the resulting number only contains 64 bits. The least
+            // significant 64 bits are only used for rounding the most significant 64
+            // bits.
+            const long kM32 = 0xFFFFFFFFL;
+            long a = F.UnsignedShift(32);
+            long b = F & kM32;
+            long c = other.F.UnsignedShift(32);
+            long d = other.F & kM32;
+            long ac = a*c;
+            long bc = b*c;
+            long ad = a*d;
+            long bd = b*d;
+            long tmp = bd.UnsignedShift(32) + (ad & kM32) + (bc & kM32);
+            // By adding 1U << 31 to tmp we round the final result.
+            // Halfway cases will be round up.
+            tmp += 1L << 31;
+            long resultF = ac + ad.UnsignedShift(32) + bc.UnsignedShift(32) + tmp.UnsignedShift(32);
+            E += other.E + 64;
+            F = resultF;
+        }
+
+        // returns a * b;
+        internal static DiyFp Times(DiyFp a, DiyFp b)
+        {
+            DiyFp result = new DiyFp(a.F, a.E);
+            result.Multiply(b);
+            return result;
+        }
+
+        internal void Normalize()
+        {
+            long f = F;
+            int e = E;
+
+            // This method is mainly called for normalizing boundaries. In general
+            // boundaries need to be shifted by 10 bits. We thus optimize for this case.
+            const long k10MsBits = 0xFFC00000L << 32;
+            while ((f & k10MsBits) == 0)
+            {
+                f <<= 10;
+                e -= 10;
+            }
+            while (((ulong) f & KUint64MSB) == 0)
+            {
+                f <<= 1;
+                e--;
+            }
+
+            F = f;
+            E = e;
+        }
+
+        public override string ToString()
+        {
+            return "[DiyFp f:" + F + ", e:" + E + "]";
+        }
+    }
+}

+ 138 - 0
Jint/Native/Number/Dtoa/DoubleHelper.cs

@@ -0,0 +1,138 @@
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Ported to Java from Mozilla's version of V8-dtoa by Hannes Wallnoefer.
+// The original revision was 67d1049b0bf9 from the mozilla-central tree.
+
+using System.Diagnostics;
+
+namespace Jint.Native.Number.Dtoa
+{
+
+// Helper functions for doubles.
+    public class DoubleHelper
+    {
+
+        private const long KExponentMask = 0x7FF0000000000000L;
+        private const long KSignificandMask = 0x000FFFFFFFFFFFFFL;
+        private const long KHiddenBit = 0x0010000000000000L;
+
+        private static DiyFp AsDiyFp(long d64)
+        {
+            Debug.Assert(!IsSpecial(d64));
+            return new DiyFp(Significand(d64), Exponent(d64));
+        }
+
+        // this->Significand() must not be 0.
+        internal static DiyFp AsNormalizedDiyFp(long d64)
+        {
+            long f = Significand(d64);
+            int e = Exponent(d64);
+
+            Debug.Assert(f != 0);
+
+            // The current double could be a denormal.
+            while ((f & KHiddenBit) == 0)
+            {
+                f <<= 1;
+                e--;
+            }
+            // Do the final shifts in one go. Don't forget the hidden bit (the '-1').
+            f <<= DiyFp.KSignificandSize - KSignificandSize - 1;
+            e -= DiyFp.KSignificandSize - KSignificandSize - 1;
+            return new DiyFp(f, e);
+        }
+
+        private static int Exponent(long d64)
+        {
+            if (IsDenormal(d64)) return KDenormalExponent;
+
+            int biasedE = (int) ((d64 & KExponentMask).UnsignedShift(KSignificandSize) & 0xffffffffL);
+            return biasedE - KExponentBias;
+        }
+
+        private static long Significand(long d64)
+        {
+            long significand = d64 & KSignificandMask;
+            if (!IsDenormal(d64))
+            {
+                return significand + KHiddenBit;
+            }
+            else
+            {
+                return significand;
+            }
+        }
+
+        // Returns true if the double is a denormal.
+        private static bool IsDenormal(long d64)
+        {
+            return (d64 & KExponentMask) == 0L;
+        }
+
+        // We consider denormals not to be special.
+        // Hence only Infinity and NaN are special.
+        private static bool IsSpecial(long d64)
+        {
+            return (d64 & KExponentMask) == KExponentMask;
+        }
+
+        // Returns the two boundaries of first argument.
+        // The bigger boundary (m_plus) is normalized. The lower boundary has the same
+        // exponent as m_plus.
+        internal static void NormalizedBoundaries(long d64, DiyFp mMinus, DiyFp mPlus)
+        {
+            DiyFp v = AsDiyFp(d64);
+            bool significandIsZero = (v.F == KHiddenBit);
+            mPlus.F = (v.F << 1) + 1;
+            mPlus.E = v.E - 1;
+            mPlus.Normalize();
+            if (significandIsZero && v.E != KDenormalExponent)
+            {
+                // The boundary is closer. Think of v = 1000e10 and v- = 9999e9.
+                // Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
+                // at a distance of 1e8.
+                // The only exception is for the smallest normal: the largest denormal is
+                // at the same distance as its successor.
+                // Note: denormals have the same exponent as the smallest normals.
+                mMinus.F = (v.F << 2) - 1;
+                mMinus.E = v.E - 2;
+            }
+            else
+            {
+                mMinus.F = (v.F << 1) - 1;
+                mMinus.E = v.E - 1;
+            }
+            mMinus.F = mMinus.F << (mMinus.E - mPlus.E);
+            mMinus.E = mPlus.E;
+        }
+
+        private const int KSignificandSize = 52; // Excludes the hidden bit.
+        private const int KExponentBias = 0x3FF + KSignificandSize;
+        private const int KDenormalExponent = -KExponentBias + 1;
+    }
+}

+ 535 - 0
Jint/Native/Number/Dtoa/FastDtoa.cs

@@ -0,0 +1,535 @@
+// Copyright 2010 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Ported to Java from Mozilla's version of V8-dtoa by Hannes Wallnoefer.
+// The original revision was 67d1049b0bf9 from the mozilla-central tree.
+
+using System;
+using System.Diagnostics;
+
+namespace Jint.Native.Number.Dtoa
+{
+    public class FastDtoa
+    {
+
+        // FastDtoa will produce at most kFastDtoaMaximalLength digits.
+        public const int KFastDtoaMaximalLength = 17;
+        
+        // The minimal and maximal target exponent define the range of w's binary
+        // exponent, where 'w' is the result of multiplying the input by a cached power
+        // of ten.
+        //
+        // A different range might be chosen on a different platform, to optimize digit
+        // generation, but a smaller range requires more powers of ten to be cached.
+        private const int MinimalTargetExponent = -60;
+        private const int MaximalTargetExponent = -32;
+
+        // Adjusts the last digit of the generated number, and screens out generated
+        // solutions that may be inaccurate. A solution may be inaccurate if it is
+        // outside the safe interval, or if we ctannot prove that it is closer to the
+        // input than a neighboring representation of the same length.
+        //
+        // Input: * buffer containing the digits of too_high / 10^kappa
+        //        * distance_too_high_w == (too_high - w).f() * unit
+        //        * unsafe_interval == (too_high - too_low).f() * unit
+        //        * rest = (too_high - buffer * 10^kappa).f() * unit
+        //        * ten_kappa = 10^kappa * unit
+        //        * unit = the common multiplier
+        // Output: returns true if the buffer is guaranteed to contain the closest
+        //    representable number to the input.
+        //  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)
+        {
+            long smallDistance = distanceTooHighW - unit;
+            long bigDistance = distanceTooHighW + unit;
+            // Let w_low  = too_high - big_distance, and
+            //     w_high = too_high - small_distance.
+            // Note: w_low < w < w_high
+            //
+            // The real w (* unit) must lie somewhere inside the interval
+            // ]w_low; w_low[ (often written as "(w_low; w_low)")
+
+            // Basically the buffer currently contains a number in the unsafe interval
+            // ]too_low; too_high[ with too_low < w < too_high
+            //
+            //  too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+            //                     ^v 1 unit            ^      ^                 ^      ^
+            //  boundary_high ---------------------     .      .                 .      .
+            //                     ^v 1 unit            .      .                 .      .
+            //   - - - - - - - - - - - - - - - - - - -  +  - - + - - - - - -     .      .
+            //                                          .      .         ^       .      .
+            //                                          .  big_distance  .       .      .
+            //                                          .      .         .       .    rest
+            //                              small_distance     .         .       .      .
+            //                                          v      .         .       .      .
+            //  w_high - - - - - - - - - - - - - - - - - -     .         .       .      .
+            //                     ^v 1 unit                   .         .       .      .
+            //  w ----------------------------------------     .         .       .      .
+            //                     ^v 1 unit                   v         .       .      .
+            //  w_low  - - - - - - - - - - - - - - - - - - - - -         .       .      .
+            //                                                           .       .      v
+            //  buffer --------------------------------------------------+-------+--------
+            //                                                           .       .
+            //                                                  safe_interval    .
+            //                                                           v       .
+            //   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -     .
+            //                     ^v 1 unit                                     .
+            //  boundary_low -------------------------                     unsafe_interval
+            //                     ^v 1 unit                                     v
+            //  too_low  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+            //
+            //
+            // Note that the value of buffer could lie anywhere inside the range too_low
+            // to too_high.
+            //
+            // boundary_low, boundary_high and w are approximations of the real boundaries
+            // and v (the input number). They are guaranteed to be precise up to one unit.
+            // In fact the error is guaranteed to be strictly less than one unit.
+            //
+            // Anything that lies outside the unsafe interval is guaranteed not to round
+            // to v when read again.
+            // Anything that lies inside the safe interval is guaranteed to round to v
+            // when read again.
+            // If the number inside the buffer lies inside the unsafe interval but not
+            // inside the safe interval then we simply do not know and bail out (returning
+            // false).
+            //
+            // Similarly we have to take into account the imprecision of 'w' when rounding
+            // the buffer. If we have two potential representations we need to make sure
+            // that the chosen one is closer to w_low and w_high since v can be anywhere
+            // between them.
+            //
+            // By generating the digits of too_high we got the largest (closest to
+            // too_high) buffer that is still in the unsafe interval. In the case where
+            // w_high < buffer < too_high we try to decrement the buffer.
+            // This way the buffer approaches (rounds towards) w.
+            // There are 3 conditions that stop the decrementation process:
+            //   1) the buffer is already below w_high
+            //   2) decrementing the buffer would make it leave the unsafe interval
+            //   3) decrementing the buffer would yield a number below w_high and farther
+            //      away than the current number. In other words:
+            //              (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high
+            // Instead of using the buffer directly we use its distance to too_high.
+            // Conceptually rest ~= too_high - buffer
+            while (rest < smallDistance && // Negated condition 1
+                   unsafeInterval - rest >= tenKappa && // Negated condition 2
+                   (rest + tenKappa < smallDistance || // buffer{-1} > w_high
+                    smallDistance - rest >= rest + tenKappa - smallDistance))
+            {
+                buffer.DecreaseLast();
+                rest += tenKappa;
+            }
+
+            // We have approached w+ as much as possible. We now test if approaching w-
+            // would require changing the buffer. If yes, then we have two possible
+            // representations close to w, but we cannot decide which one is closer.
+            if (rest < bigDistance &&
+                unsafeInterval - rest >= tenKappa &&
+                (rest + tenKappa < bigDistance ||
+                 bigDistance - rest > rest + tenKappa - bigDistance))
+            {
+                return false;
+            }
+
+            // Weeding test.
+            //   The safe interval is [too_low + 2 ulp; too_high - 2 ulp]
+            //   Since too_low = too_high - unsafe_interval this is equivalent to
+            //      [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp]
+            //   Conceptually we have: rest ~= too_high - buffer
+            return (2*unit <= rest) && (rest <= unsafeInterval - 4*unit);
+        }
+        
+        private const int KTen4 = 10000;
+        private const int KTen5 = 100000;
+        private const int KTen6 = 1000000;
+        private const int KTen7 = 10000000;
+        private const int KTen8 = 100000000;
+        private const int KTen9 = 1000000000;
+
+        // Returns the biggest power of ten that is less than or equal than the given
+        // number. We furthermore receive the maximum number of bits 'number' has.
+        // If number_bits == 0 then 0^-1 is returned
+        // The number of bits must be <= 32.
+        // Precondition: (1 << number_bits) <= number < (1 << (number_bits + 1)).
+        private static long BiggestPowerTen(int number,
+            int numberBits)
+        {
+            int power, exponent;
+            switch (numberBits)
+            {
+                case 32:
+                case 31:
+                case 30:
+                    if (KTen9 <= number)
+                    {
+                        power = KTen9;
+                        exponent = 9;
+                        break;
+                    } // else fallthrough
+
+                    goto case 29;
+                case 29:
+                case 28:
+                case 27:
+                    if (KTen8 <= number)
+                    {
+                        power = KTen8;
+                        exponent = 8;
+                        break;
+                    } // else fallthrough
+                    goto case 26;
+                case 26:
+                case 25:
+                case 24:
+                    if (KTen7 <= number)
+                    {
+                        power = KTen7;
+                        exponent = 7;
+                        break;
+                    } // else fallthrough
+                    goto case 23;
+                case 23:
+                case 22:
+                case 21:
+                case 20:
+                    if (KTen6 <= number)
+                    {
+                        power = KTen6;
+                        exponent = 6;
+                        break;
+                    } // else fallthrough
+                    goto case 19;
+                case 19:
+                case 18:
+                case 17:
+                    if (KTen5 <= number)
+                    {
+                        power = KTen5;
+                        exponent = 5;
+                        break;
+                    } // else fallthrough
+                    goto case 16;
+                case 16:
+                case 15:
+                case 14:
+                    if (KTen4 <= number)
+                    {
+                        power = KTen4;
+                        exponent = 4;
+                        break;
+                    } // else fallthrough
+                    goto case 13;
+                case 13:
+                case 12:
+                case 11:
+                case 10:
+                    if (1000 <= number)
+                    {
+                        power = 1000;
+                        exponent = 3;
+                        break;
+                    } // else fallthrough
+                    goto case 9;
+                case 9:
+                case 8:
+                case 7:
+                    if (100 <= number)
+                    {
+                        power = 100;
+                        exponent = 2;
+                        break;
+                    } // else fallthrough
+                    goto case 6;
+                case 6:
+                case 5:
+                case 4:
+                    if (10 <= number)
+                    {
+                        power = 10;
+                        exponent = 1;
+                        break;
+                    } // else fallthrough
+                    goto case 3;
+                case 3:
+                case 2:
+                case 1:
+                    if (1 <= number)
+                    {
+                        power = 1;
+                        exponent = 0;
+                        break;
+                    } // else fallthrough
+                    goto case 0;
+                case 0:
+                    power = 0;
+                    exponent = -1;
+                    break;
+                default:
+                    // Following assignments are here to silence compiler warnings.
+                    power = 0;
+                    exponent = 0;
+                    // UNREACHABLE();
+                    break;
+            }
+            return ((long) power << 32) | (0xffffffffL & exponent);
+        }
+
+        // Generates the digits of input number w.
+        // w is a floating-point number (DiyFp), consisting of a significand and an
+        // exponent. Its exponent is bounded by minimal_target_exponent and
+        // maximal_target_exponent.
+        //       Hence -60 <= w.e() <= -32.
+        //
+        // Returns false if it fails, in which case the generated digits in the buffer
+        // should not be used.
+        // Preconditions:
+        //  * low, w and high are correct up to 1 ulp (unit in the last place). That
+        //    is, their error must be less that a unit of their last digits.
+        //  * low.e() == w.e() == high.e()
+        //  * low < w < high, and taking into account their error: low~ <= high~
+        //  * minimal_target_exponent <= w.e() <= maximal_target_exponent
+        // Postconditions: returns false if procedure fails.
+        //   otherwise:
+        //     * buffer is not null-terminated, but len contains the number of digits.
+        //     * buffer contains the shortest possible decimal digit-sequence
+        //       such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
+        //       correct values of low and high (without their error).
+        //     * if more than one decimal representation gives the minimal number of
+        //       decimal digits then the one closest to W (where W is the correct value
+        //       of w) is chosen.
+        // 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 (~0.5%).
+        //
+        // Say, for the sake of example, that
+        //   w.e() == -48, and w.f() == 0x1234567890abcdef
+        // w's value can be computed by w.f() * 2^w.e()
+        // We can obtain w's integral digits by simply shifting w.f() by -w.e().
+        //  -> w's integral part is 0x1234
+        //  w's fractional part is therefore 0x567890abcdef.
+        // Printing w's integral part is easy (simply print 0x1234 in decimal).
+        // In order to print its fraction we repeatedly multiply the fraction by 10 and
+        // get each digit. Example the first digit after the point would be computed by
+        //   (0x567890abcdef * 10) >> 48. -> 3
+        // The whole thing becomes slightly more complicated because we want to stop
+        // once we have enough digits. That is, once the digits inside the buffer
+        // represent 'w' we can stop. Everything inside the interval low - high
+        // represents w. However we have to pay attention to low, high and w's
+        // imprecision.
+        private static bool DigitGen(DiyFp low,
+            DiyFp w,
+            DiyFp high,
+            FastDtoaBuilder buffer,
+            int mk)
+        {
+            // low, w and high are imprecise, but by less than one ulp (unit in the last
+            // place).
+            // If we remove (resp. add) 1 ulp from low (resp. high) we are certain that
+            // the new numbers are outside of the interval we want the final
+            // representation to lie in.
+            // Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield
+            // numbers that are certain to lie in the interval. We will use this fact
+            // later on.
+            // We will now start by generating the digits within the uncertain
+            // interval. Later we will weed out representations that lie outside the safe
+            // interval and thus _might_ lie outside the correct interval.
+            long unit = 1;
+            var tooLow = new DiyFp(low.F - unit, low.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
+            // generated number in.
+            var unsafeInterval = DiyFp.Minus(tooHigh, tooLow);
+            // We now cut the input number into two parts: the integral digits and the
+            // fractionals. We will not write any decimal separator though, but adapt
+            // kappa instead.
+            // Reminder: we are currently computing the digits (stored inside the buffer)
+            // such that:   too_low < buffer * 10^kappa < too_high
+            // We use too_high for the digit_generation and stop as soon as possible.
+            // If we stop early we effectively round down.
+            var one = new DiyFp(1L << -w.E, w.E);
+            // Division by one is a shift.
+            var integrals = (int) (tooHigh.F.UnsignedShift(-one.E) & 0xffffffffL);
+            // 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;
+            // Loop invariant: buffer = too_high / 10^kappa  (integer division)
+            // 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
+            // that is smaller than integrals.
+            while (kappa > 0)
+            {
+                int digit = integrals/divider;
+                buffer.Append((char) ('0' + digit));
+                integrals %= divider;
+                kappa--;
+                // Note that kappa now equals the exponent of the divider and that the
+                // invariant thus holds again.
+                long rest =
+                    ((long) integrals << -one.E) + fractionals;
+                // Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
+                // Reminder: unsafe_interval.e() == one.e()
+                if (rest < unsafeInterval.F)
+                {
+                    // Rounding down (by not emitting the remaining digits) yields a number
+                    // 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);
+                }
+                divider /= 10;
+            }
+
+            // 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 (like the interval or 'unit'), too.
+            // Instead of multiplying by 10 we multiply by 5 (cheaper operation) and
+            // increase its (imaginary) exponent. At the same time we decrease the
+            // divider's (one's) exponent and shift its significand.
+            // Basically, if fractionals was a DiyFp (with fractionals.e == one.e):
+            //      fractionals.f *= 10;
+            //      fractionals.f >>= 1; fractionals.e++; // value remains unchanged.
+            //      one.f >>= 1; one.e++;                 // value remains unchanged.
+            //      and we have again fractionals.e == one.e which allows us to divide
+            //           fractionals.f() by one.f()
+            // We simply combine the *= 10 and the >>= 1.
+            while (true)
+            {
+                fractionals *= 5;
+                unit *= 5;
+                unsafeInterval.F = unsafeInterval.F*5;
+                unsafeInterval.E = unsafeInterval.E + 1; // Will be optimized out.
+                one.F = one.F.UnsignedShift(1);
+                one.E = one.E + 1;
+                // Integer division by one.
+                var digit = (int) ((fractionals.UnsignedShift(-one.E)) & 0xffffffffL);
+                buffer.Append((char) ('0' + digit));
+                fractionals &= one.F - 1; // Modulo by one.
+                kappa--;
+                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);
+                }
+            }
+        }
+
+        // Provides a decimal representation of v.
+        // Returns true if it succeeds, otherwise the result cannot be trusted.
+        // There will be *length digits inside the buffer (not null-terminated).
+        // If the function returns true then
+        //        v == (double) (buffer * 10^decimal_exponent).
+        // The digits in the buffer are the shortest representation possible: no
+        // 0.09999999999999999 instead of 0.1. The shorter representation will even be
+        // chosen even if the longer one would be closer to v.
+        // 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
+        // computed.
+        private static bool Grisu3(double v, FastDtoaBuilder buffer)
+        {
+            long bits = BitConverter.DoubleToInt64Bits(v);
+            DiyFp w = DoubleHelper.AsNormalizedDiyFp(bits);
+            // boundary_minus and boundary_plus are the boundaries between v and its
+            // closest floating-point neighbors. Any number strictly between
+            // boundary_minus and boundary_plus will round to v when convert to a double.
+            // Grisu3 will never output representations that lie exactly on a boundary.
+            DiyFp boundaryMinus = new DiyFp(), boundaryPlus = new DiyFp();
+            DoubleHelper.NormalizedBoundaries(bits, boundaryMinus, boundaryPlus);
+            Debug.Assert(boundaryPlus.E == w.E);
+            var tenMk = new DiyFp(); // Cached power of ten: 10^-k
+            int mk = CachedPowers.GetCachedPower(w.E + DiyFp.KSignificandSize,
+                MinimalTargetExponent, MaximalTargetExponent, tenMk);
+            Debug.Assert(MinimalTargetExponent <= w.E + tenMk.E +
+                         DiyFp.KSignificandSize &&
+                         MaximalTargetExponent >= w.E + tenMk.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 scaledW = DiyFp.Times(w, tenMk);
+            Debug.Assert(scaledW.E ==
+                         boundaryPlus.E + tenMk.E + DiyFp.KSignificandSize);
+            // In theory it would be possible to avoid some recomputations by computing
+            // the difference between w and boundary_minus/plus (a power of 2) and to
+            // compute scaled_boundary_minus/plus by subtracting/adding from
+            // scaled_w. However the code becomes much less readable and the speed
+            // enhancements are not terriffic.
+            DiyFp scaledBoundaryMinus = DiyFp.Times(boundaryMinus, tenMk);
+            DiyFp scaledBoundaryPlus = DiyFp.Times(boundaryPlus, tenMk);
+
+            // DigitGen will generate the digits of scaled_w. Therefore we have
+            // v == (double) (scaled_w * 10^-mk).
+            // Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
+            // 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
+            // decreased by 2.
+            return DigitGen(scaledBoundaryMinus, scaledW, scaledBoundaryPlus, buffer, mk);
+        }
+
+        public static bool Dtoa(double v, FastDtoaBuilder buffer)
+        {
+            Debug.Assert(v > 0);
+            Debug.Assert(!Double.IsNaN(v));
+            Debug.Assert(!Double.IsInfinity(v));
+
+            return Grisu3(v, buffer);
+        }
+
+        public static string NumberToString(double v)
+        {
+            var buffer = new FastDtoaBuilder();
+            return NumberToString(v, buffer) ? buffer.Format() : null;
+        }
+
+        public static bool NumberToString(double v, FastDtoaBuilder buffer)
+        {
+            buffer.Reset();
+            if (v < 0)
+            {
+                buffer.Append('-');
+                v = -v;
+            }
+            return Dtoa(v, buffer);
+        }
+    }
+}

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

@@ -0,0 +1,138 @@
+/* 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
+{
+    public class FastDtoaBuilder
+    {
+
+        // allocate buffer for generated digits + extra notation + padding zeroes
+        private readonly char[] _chars = new char[FastDtoa.KFastDtoaMaximalLength + 8];
+        internal int End = 0;
+        internal int Point;
+        private bool _formatted;
+
+        internal void Append(char c)
+        {
+            _chars[End++] = c;
+        }
+
+        internal void DecreaseLast()
+        {
+            _chars[End - 1]--;
+        }
+
+        public void Reset()
+        {
+            End = 0;
+            _formatted = false;
+        }
+
+        public override string ToString()
+        {
+            return "[chars:" + new System.String(_chars, 0, End) + ", point:" + Point + "]";
+        }
+
+        public System.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 System.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 void Fill<T>(T[] array, int fromIndex, int toIndex, T val)
+        {
+            for (int i = fromIndex; i < toIndex; i++)
+            {
+                array[i] = val;
+            }
+        }
+    }
+}

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

@@ -0,0 +1,10 @@
+namespace Jint.Native.Number.Dtoa
+{
+    public static class NumberExtensions
+    {
+        public static long UnsignedShift(this long l, int shift)
+        {
+            return (long) ((ulong) l >> shift);
+        }
+    }
+}

+ 9 - 0
Jint/Native/Number/NumberPrototype.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Globalization;
 using System.Text;
+using Jint.Native.Number.Dtoa;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 
@@ -288,6 +289,14 @@ namespace Jint.Native.Number
                 return "-" + ToNumberString(-m);
             }
 
+            // 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;
+            }
+
             // s is all digits (significand)
             // k number of digits of s
             // n total of digits in fraction s*10^n-k=m

+ 5 - 7
README.md

@@ -61,9 +61,9 @@ If you need to pass a JavaScript callback to the CLR, then it will be converted
         
 # Roadmap
 
-## Features already implemented:
+## Features:
 
-- ECMAScript test suite (http://test262.ecmascript.org/) 
+- ECMAScript 5.1 test suite (http://test262.ecmascript.org/) 
 - Manipulate CLR objects from JavaScript, including:
   - Single values
   - Objects
@@ -82,9 +82,7 @@ If you need to pass a JavaScript callback to the CLR, then it will be converted
   - Regex -> RegExp
   - Function -> Delegate
 
-## Current tasks:
-
-- Fix remaining SunSpider scripts
-  - 3d-raytrace
-
+## Roadmap:
 
+- Instantiate CLR classes from Javascript
+- ECMAScript 6.0