Browse Source

Improve parseFloat performance and number range (#1396)

fixes https://github.com/sebastienros/jint/issues/1395
Marko Lahma 2 years ago
parent
commit
297a9f4dd6
2 changed files with 80 additions and 102 deletions
  1. 8 0
      Jint.Tests/Runtime/NumberTests.cs
  2. 72 102
      Jint/Native/Global/GlobalObject.cs

+ 8 - 0
Jint.Tests/Runtime/NumberTests.cs

@@ -55,5 +55,13 @@ namespace Jint.Tests.Runtime
             var value = _engine.Evaluate($"(3).toPrecision({fractionDigits}).toString()").AsString();
             var value = _engine.Evaluate($"(3).toPrecision({fractionDigits}).toString()").AsString();
             Assert.Equal(result, value);
             Assert.Equal(result, value);
         }
         }
+
+        [Theory]
+        [InlineData("1.7976931348623157e+308", double.MaxValue)]
+        public void ParseFloat(string input, double result)
+        {
+            var value = _engine.Evaluate($"parseFloat('{input}')").AsNumber();
+            Assert.Equal(result, value);
+        }
     }
     }
 }
 }

+ 72 - 102
Jint/Native/Global/GlobalObject.cs

@@ -1,6 +1,7 @@
 using System.Globalization;
 using System.Globalization;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text;
+using Esprima;
 using Jint.Collections;
 using Jint.Collections;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Native.String;
 using Jint.Native.String;
@@ -224,154 +225,124 @@ namespace Jint.Native.Global
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.3
+        /// https://tc39.es/ecma262/#sec-parsefloat-string
         /// </summary>
         /// </summary>
         public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments)
         public static 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);
 
 
-            var sign = 1;
-            if (trimmedString.Length > 0)
-            {
-                if (trimmedString[0] == '-')
-                {
-                    sign = -1;
-                    trimmedString = trimmedString.Substring(1);
-                }
-                else if (trimmedString[0] == '+')
-                {
-                    trimmedString = trimmedString.Substring(1);
-                }
-            }
-
-            if (trimmedString.StartsWith("Infinity"))
-            {
-                return sign * double.PositiveInfinity;
-            }
-
-            if (trimmedString.StartsWith("NaN"))
+            if (string.IsNullOrWhiteSpace(trimmedString))
             {
             {
                 return JsNumber.DoubleNaN;
                 return JsNumber.DoubleNaN;
             }
             }
 
 
-            var separator = (char)0;
-
-            bool isNan = true;
-            decimal number = 0;
+            // start of string processing
             var i = 0;
             var i = 0;
-            for (; i < trimmedString.Length; i++)
+
+            // check known string constants
+            if (!char.IsDigit(trimmedString[0]))
             {
             {
-                var c = trimmedString[i];
-                if (c == '.')
+                if (trimmedString[0] == '-')
                 {
                 {
                     i++;
                     i++;
-                    separator = '.';
-                    break;
+                    if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("-Infinity"))
+                    {
+                        return JsNumber.DoubleNegativeInfinity;
+                    }
                 }
                 }
 
 
-                if (c == 'e' || c == 'E')
+                if (trimmedString[0] == '+')
                 {
                 {
                     i++;
                     i++;
-                    separator = 'e';
-                    break;
+                    if (trimmedString.Length > 1 && trimmedString[1] == 'I' && trimmedString.StartsWith("+Infinity"))
+                    {
+                        return JsNumber.DoublePositiveInfinity;
+                    }
                 }
                 }
 
 
-                var digit = c - '0';
-
-                if (digit >= 0 && digit <= 9)
+                if (trimmedString.StartsWith("Infinity"))
                 {
                 {
-                    isNan = false;
-                    number = number * 10 + digit;
+                    return JsNumber.DoublePositiveInfinity;
                 }
                 }
-                else
+
+                if (trimmedString.StartsWith("NaN"))
                 {
                 {
-                    break;
+                    return JsNumber.DoubleNaN;
                 }
                 }
             }
             }
 
 
-            decimal pow = 0.1m;
+            // find the starting part of string  that is still acceptable JS number
 
 
-            if (separator == '.')
+            var dotFound = false;
+            var exponentFound = false;
+            while (i < trimmedString.Length)
             {
             {
-                for (; i < trimmedString.Length; i++)
-                {
-                    var c = trimmedString[i];
+                var c = trimmedString[i];
 
 
-                    var digit = c - '0';
+                if (Character.IsDecimalDigit(c))
+                {
+                    i++;
+                    continue;
+                }
 
 
-                    if (digit >= 0 && digit <= 9)
-                    {
-                        isNan = false;
-                        number += digit * pow;
-                        pow *= 0.1m;
-                    }
-                    else if (c == 'e' || c == 'E')
-                    {
-                        i++;
-                        separator = 'e';
-                        break;
-                    }
-                    else
+                if (c == '.')
+                {
+                    if (dotFound)
                     {
                     {
+                        // does not look right
                         break;
                         break;
                     }
                     }
-                }
-            }
 
 
-            var exp = 0;
-            var expSign = 1;
+                    i++;
+                    dotFound = true;
+                    continue;
+                }
 
 
-            if (separator == 'e')
-            {
-                if (i < trimmedString.Length)
+                if (c is 'e' or 'E')
                 {
                 {
-                    if (trimmedString[i] == '-')
-                    {
-                        expSign = -1;
-                        i++;
-                    }
-                    else if (trimmedString[i] == '+')
+                    if (exponentFound)
                     {
                     {
-                        i++;
+                        // does not look right
+                        break;
                     }
                     }
+
+                    i++;
+                    exponentFound = true;
+                    continue;
                 }
                 }
 
 
-                for (; i < trimmedString.Length; i++)
+                if (c is '+' or '-' && trimmedString[i - 1] is 'e' or 'E')
                 {
                 {
-                    var c = trimmedString[i];
-
-                    var digit = c - '0';
-
-                    if (digit >= 0 && digit <= 9)
-                    {
-                        exp = exp * 10 + digit;
-                    }
-                    else
-                    {
-                        break;
-                    }
+                    // ok
+                    i++;
+                    continue;
                 }
                 }
+
+                break;
             }
             }
 
 
-            if (isNan)
+            while (exponentFound && i > 0 && !Character.IsDecimalDigit(trimmedString[i - 1]))
             {
             {
-                return JsNumber.DoubleNaN;
+                // we are missing required exponent number part info
+                i--;
             }
             }
 
 
-            for (var k = 1; k <= exp; k++)
+            // we should now have proper input part
+
+#if NETSTANDARD2_1_OR_GREATER
+            var substring = trimmedString.AsSpan(0, i);
+#else
+            var substring = trimmedString.Substring(0, i);
+#endif
+
+            const NumberStyles Styles = NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign;
+            if (double.TryParse(substring, Styles, CultureInfo.InvariantCulture, out var d))
             {
             {
-                if (expSign > 0)
-                {
-                    number *= 10;
-                }
-                else
-                {
-                    number /= 10;
-                }
+                return d;
             }
             }
 
 
-            return (double)(sign * number);
+            return JsNumber.DoubleNaN;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -550,11 +521,10 @@ namespace Jint.Native.Global
                         };
                         };
                     }
                     }
 
 
-                    for (var j = 0; j < octets.Length; j++)
+                    foreach (var octet in octets)
                     {
                     {
-                        var jOctet = octets[j];
-                        var x1 = HexaMap[jOctet / 16];
-                        var x2 = HexaMap[jOctet % 16];
+                        var x1 = HexaMap[octet / 16];
+                        var x2 = HexaMap[octet % 16];
                         _stringBuilder.Append('%').Append(x1).Append(x2);
                         _stringBuilder.Append('%').Append(x1).Append(x2);
                     }
                     }
                 }
                 }