|
@@ -1,4 +1,6 @@
|
|
|
using System;
|
|
|
+using System.Globalization;
|
|
|
+using System.Text;
|
|
|
using Jint.Runtime;
|
|
|
using Jint.Runtime.Interop;
|
|
|
|
|
@@ -36,9 +38,41 @@ namespace Jint.Native.Number
|
|
|
FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, ToPrecision), true, false, true);
|
|
|
}
|
|
|
|
|
|
- private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)
|
|
|
+ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
|
|
|
{
|
|
|
- throw new System.NotImplementedException();
|
|
|
+ if (!thisObject.IsNumber() && (thisObject.TryCast<NumberInstance>() == null))
|
|
|
+ {
|
|
|
+ throw new JavaScriptException(Engine.TypeError);
|
|
|
+ }
|
|
|
+
|
|
|
+ var m = TypeConverter.ToNumber(thisObject);
|
|
|
+
|
|
|
+ if (double.IsNaN(m))
|
|
|
+ {
|
|
|
+ return "NaN";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m.Equals(0))
|
|
|
+ {
|
|
|
+ return "0";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (m < 0)
|
|
|
+ {
|
|
|
+ return "-" + ToNumberString(-m);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
|
|
|
+ {
|
|
|
+ return "Infinity";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (double.IsNegativeInfinity(m) || m <= -double.MaxValue)
|
|
|
+ {
|
|
|
+ return "-Infinity";
|
|
|
+ }
|
|
|
+
|
|
|
+ return m.ToString("n", Engine.Options.GetCulture());
|
|
|
}
|
|
|
|
|
|
private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
|
|
@@ -52,6 +86,8 @@ namespace Jint.Native.Number
|
|
|
return number.PrimitiveValue;
|
|
|
}
|
|
|
|
|
|
+ private const double Ten21 = 1e21;
|
|
|
+
|
|
|
private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
|
|
|
{
|
|
|
var f = (int)TypeConverter.ToInteger(arguments.At(0, 0));
|
|
@@ -67,61 +103,71 @@ namespace Jint.Native.Number
|
|
|
return "NaN";
|
|
|
}
|
|
|
|
|
|
- var sign = "";
|
|
|
- if (x < 0)
|
|
|
+ if (x >= Ten21)
|
|
|
{
|
|
|
- sign = "-";
|
|
|
- x = -x;
|
|
|
+ return ToNumberString(x);
|
|
|
}
|
|
|
+
|
|
|
+ var l = (long) x; // extract integer part
|
|
|
|
|
|
- string m = "";
|
|
|
- if (x >= System.Math.Pow(10, 21))
|
|
|
- {
|
|
|
- m = TypeConverter.ToString(x);
|
|
|
- }
|
|
|
- else
|
|
|
+ if (f == 0)
|
|
|
{
|
|
|
- var d = (Decimal)x;
|
|
|
- var significants = GetSignificantDigitCount(d);
|
|
|
- var s = significants.Item1;
|
|
|
- var kdigits = (int)System.Math.Floor(System.Math.Log10((double)s) + 1);
|
|
|
- var n = kdigits - significants.Item2;
|
|
|
-
|
|
|
- if (n == 0)
|
|
|
- {
|
|
|
- m = "0";
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- m = (System.Math.Round(x, f) * System.Math.Pow(10,f)).ToString();
|
|
|
- }
|
|
|
-
|
|
|
- if (f != 0)
|
|
|
- {
|
|
|
- var k = m.Length;
|
|
|
- if (k <= f)
|
|
|
- {
|
|
|
- var z = new System.String('0', f + 1 - k);
|
|
|
- m += z;
|
|
|
- k = f + 1;
|
|
|
- }
|
|
|
- var a = m.Substring(0, k - f);
|
|
|
- var b = m.Substring(k - f);
|
|
|
- m = a + "." + b;
|
|
|
- }
|
|
|
+ return l.ToString(CultureInfo.InvariantCulture);
|
|
|
}
|
|
|
|
|
|
- return sign + m;
|
|
|
+ var d = x - l;
|
|
|
+ return l.ToString(CultureInfo.InvariantCulture) + d.ToString("." + new string('0', f), CultureInfo.InvariantCulture);
|
|
|
}
|
|
|
|
|
|
private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
|
|
|
{
|
|
|
- throw new System.NotImplementedException();
|
|
|
+ var f = (int)TypeConverter.ToInteger(arguments.At(0, 16));
|
|
|
+ if (f < 0 || f > 20)
|
|
|
+ {
|
|
|
+ throw new JavaScriptException(Engine.RangeError, "fractionDigits argument must be between 0 and 20");
|
|
|
+ }
|
|
|
+
|
|
|
+ var x = TypeConverter.ToNumber(thisObj);
|
|
|
+
|
|
|
+ if (double.IsNaN(x))
|
|
|
+ {
|
|
|
+ return "NaN";
|
|
|
+ }
|
|
|
+
|
|
|
+ string format = System.String.Concat("#.", new System.String('0', f), "e+0");
|
|
|
+ return x.ToString(format, CultureInfo.InvariantCulture);
|
|
|
}
|
|
|
|
|
|
private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)
|
|
|
{
|
|
|
- throw new System.NotImplementedException();
|
|
|
+ var x = TypeConverter.ToNumber(thisObj);
|
|
|
+
|
|
|
+ if (arguments.At(0) == Undefined.Instance)
|
|
|
+ {
|
|
|
+ return TypeConverter.ToString(x);
|
|
|
+ }
|
|
|
+
|
|
|
+ var p = TypeConverter.ToInteger(arguments.At(0));
|
|
|
+
|
|
|
+ if (double.IsInfinity(x) || double.IsNaN(x))
|
|
|
+ {
|
|
|
+ return TypeConverter.ToString(x);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (p < 1 || p > 21)
|
|
|
+ {
|
|
|
+ throw new JavaScriptException(Engine.RangeError, "precision must be between 1 and 21");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the number of decimals
|
|
|
+ string str = x.ToString("e23", CultureInfo.InvariantCulture);
|
|
|
+ int decimals = str.IndexOfAny(new [] { '.', 'e' });
|
|
|
+ decimals = decimals == -1 ? str.Length : decimals;
|
|
|
+
|
|
|
+ p -= decimals;
|
|
|
+ p = p < 1 ? 1 : p;
|
|
|
+
|
|
|
+ return x.ToString("f" + p, CultureInfo.InvariantCulture);
|
|
|
}
|
|
|
|
|
|
private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments)
|
|
@@ -131,127 +177,167 @@ namespace Jint.Native.Number
|
|
|
throw new JavaScriptException(Engine.TypeError);
|
|
|
}
|
|
|
|
|
|
- return ToNumberString(TypeConverter.ToNumber(thisObject));
|
|
|
- }
|
|
|
+ var radix = arguments.At(0) == JsValue.Undefined ? 10 : (int) TypeConverter.ToInteger(arguments.At(0));
|
|
|
|
|
|
- public static string ToNumberString(double m)
|
|
|
- {
|
|
|
- if (double.IsNaN(m))
|
|
|
+ if (radix < 2 || radix > 36)
|
|
|
{
|
|
|
- return "NaN";
|
|
|
+ throw new JavaScriptException(Engine.RangeError, "radix must be between 2 and 36");
|
|
|
}
|
|
|
|
|
|
- if (m == 0)
|
|
|
+ var x = TypeConverter.ToNumber(thisObject);
|
|
|
+
|
|
|
+ if (double.IsNaN(x))
|
|
|
{
|
|
|
- return "0";
|
|
|
+ return "NaN";
|
|
|
}
|
|
|
|
|
|
- if (m < 0)
|
|
|
+ if (x.Equals(0))
|
|
|
{
|
|
|
- return "-" + ToNumberString(-m);
|
|
|
+ return "0";
|
|
|
}
|
|
|
|
|
|
- if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
|
|
|
+ if (double.IsPositiveInfinity(x) || x >= double.MaxValue)
|
|
|
{
|
|
|
return "Infinity";
|
|
|
}
|
|
|
|
|
|
- if (double.IsNegativeInfinity(m) || m <= -double.MaxValue)
|
|
|
+ if (x < 0)
|
|
|
{
|
|
|
- return "-Infinity";
|
|
|
+ return "-" + ToNumberString(-x, arguments);
|
|
|
}
|
|
|
|
|
|
- // s is all digits (significand or mantissa)
|
|
|
- // 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
|
|
|
- var d = (Decimal)m;
|
|
|
- var significants = GetSignificantDigitCount(d);
|
|
|
- var s = significants.Item1;
|
|
|
- var k = (int)System.Math.Floor(System.Math.Log10((double)s) + 1);
|
|
|
- var n = k - significants.Item2;
|
|
|
- if (m < 1 && m > -1)
|
|
|
+ if (radix == 10)
|
|
|
{
|
|
|
- n++;
|
|
|
+ return ToNumberString(x);
|
|
|
}
|
|
|
|
|
|
- while (s % 10 == 0)
|
|
|
+ const string format = "0.00000000000000000e0";
|
|
|
+ var parts = x.ToString(format, CultureInfo.InvariantCulture).Split('e');
|
|
|
+ var s = parts[0].TrimEnd('0').Replace(".", "");
|
|
|
+ var n = int.Parse(parts[1]) + 1;
|
|
|
+
|
|
|
+ var integerPart = s.Substring(0, n);
|
|
|
+
|
|
|
+ var integer = long.Parse(integerPart);
|
|
|
+ var fraction = x - integer;
|
|
|
+
|
|
|
+ string result = ToBase(integer, radix);
|
|
|
+ if (!fraction.Equals(0))
|
|
|
{
|
|
|
- s = s / 10;
|
|
|
- k--;
|
|
|
+ result += "." + ToFractionBase(fraction, radix);
|
|
|
}
|
|
|
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
- if (k <= n && n <= 21)
|
|
|
+ public static string ToBase(long n, int radix)
|
|
|
+ {
|
|
|
+ const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
|
+ if (n == 0)
|
|
|
{
|
|
|
- return s + new string('0', n - k);
|
|
|
+ return "0";
|
|
|
}
|
|
|
|
|
|
- if (0 < n && n <= 21)
|
|
|
+ var result = new StringBuilder();
|
|
|
+ while (n > 0)
|
|
|
{
|
|
|
- return s.ToString().Substring(0, n) + '.' + s.ToString().Substring(n);
|
|
|
+ var digit = (int)n % radix;
|
|
|
+ n = n / radix;
|
|
|
+ result.Insert(0, digits[digit].ToString());
|
|
|
}
|
|
|
|
|
|
- if (-6 < n && n <= 0)
|
|
|
+ return result.ToString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string ToFractionBase(double n, int radix)
|
|
|
+ {
|
|
|
+ // based on the repeated multiplication method
|
|
|
+ // http://www.mathpath.org/concepts/Num/frac.htm
|
|
|
+
|
|
|
+ const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
|
+ if (n.Equals(0))
|
|
|
{
|
|
|
- return "0." + new string('0', -n) + s;
|
|
|
+ return "0";
|
|
|
}
|
|
|
|
|
|
- if (k == 1)
|
|
|
+ var result = new StringBuilder();
|
|
|
+ while (n > 0 && result.Length < 50) // arbitrary limit
|
|
|
{
|
|
|
- return s + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
|
|
|
+ var c = n*radix;
|
|
|
+ var d = (int) c;
|
|
|
+ n = c - d;
|
|
|
+
|
|
|
+ result.Append(digits[d].ToString());
|
|
|
}
|
|
|
|
|
|
- return s.ToString().Substring(0, 1) + "." + s.ToString().Substring(1) + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
|
|
|
+ return result.ToString();
|
|
|
}
|
|
|
|
|
|
- public static Tuple<decimal, int> GetSignificantDigitCount(decimal value)
|
|
|
+ public static string ToNumberString(double m)
|
|
|
{
|
|
|
- /* So, the decimal type is basically represented as a fraction of two
|
|
|
- * integers: a numerator that can be anything, and a denominator that is
|
|
|
- * some power of 10.
|
|
|
- *
|
|
|
- * For example, the following numbers are represented by
|
|
|
- * the corresponding fractions:
|
|
|
- *
|
|
|
- * VALUE NUMERATOR DENOMINATOR
|
|
|
- * 1 1 1
|
|
|
- * 1.0 10 10
|
|
|
- * 1.012 1012 1000
|
|
|
- * 0.04 4 100
|
|
|
- * 12.01 1201 100
|
|
|
- *
|
|
|
- * So basically, if the magnitude is greater than or equal to one,
|
|
|
- * the number of digits is the number of digits in the numerator.
|
|
|
- * If it's less than one, the number of digits is the number of digits
|
|
|
- * in the denominator.
|
|
|
- */
|
|
|
+ if (double.IsNaN(m))
|
|
|
+ {
|
|
|
+ return "NaN";
|
|
|
+ }
|
|
|
|
|
|
- int[] bits = decimal.GetBits(value);
|
|
|
- int scalePart = bits[3];
|
|
|
- int highPart = bits[2];
|
|
|
- int middlePart = bits[1];
|
|
|
- int lowPart = bits[0];
|
|
|
+ if (m.Equals(0))
|
|
|
+ {
|
|
|
+ return "0";
|
|
|
+ }
|
|
|
|
|
|
- if (value >= 1M || value <= -1M)
|
|
|
+ if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
|
|
|
{
|
|
|
+ return "Infinity";
|
|
|
+ }
|
|
|
|
|
|
- var num = new decimal(lowPart, middlePart, highPart, false, 0);
|
|
|
+ if (m < 0)
|
|
|
+ {
|
|
|
+ return "-" + ToNumberString(-m);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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");
|
|
|
+ if (rFormat.IndexOf("e", StringComparison.OrdinalIgnoreCase) == -1)
|
|
|
+ {
|
|
|
+ s = rFormat.Replace(".", "").TrimStart('0').TrimEnd('0');
|
|
|
+ }
|
|
|
+
|
|
|
+ const string format = "0.00000000000000000e0";
|
|
|
+ var parts = m.ToString(format, CultureInfo.InvariantCulture).Split('e');
|
|
|
+ if (s == null)
|
|
|
+ {
|
|
|
+ s = parts[0].TrimEnd('0').Replace(".", "");
|
|
|
+ }
|
|
|
|
|
|
- return new Tuple<decimal, int>(num, (scalePart >> 16) & 0x7fff);
|
|
|
+ var n = int.Parse(parts[1]) + 1;
|
|
|
+ var k = s.Length;
|
|
|
+
|
|
|
+ if (k <= n && n <= 21)
|
|
|
+ {
|
|
|
+ return s + new string('0', n - k);
|
|
|
}
|
|
|
- else
|
|
|
+
|
|
|
+ if (0 < n && n <= 21)
|
|
|
{
|
|
|
+ return s.Substring(0, n) + '.' + s.Substring(n);
|
|
|
+ }
|
|
|
|
|
|
- // Accoring to MSDN, the exponent is represented by
|
|
|
- // bits 16-23 (the 2nd word):
|
|
|
- // http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx
|
|
|
- int exponent = (scalePart & 0x00FF0000) >> 16;
|
|
|
+ if (-6 < n && n <= 0)
|
|
|
+ {
|
|
|
+ return "0." + new string('0', -n) + s;
|
|
|
+ }
|
|
|
|
|
|
- return new Tuple<decimal, int>(lowPart, exponent + 1);
|
|
|
+ if (k == 1)
|
|
|
+ {
|
|
|
+ return s + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
+ return s.Substring(0, 1) + "." + s.Substring(1) + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
|
|
|
+ }
|
|
|
}
|
|
|
}
|