123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- using System.Diagnostics;
- using System.Globalization;
- using System.Text;
- using Jint.Native.Number.Dtoa;
- using Jint.Native.Object;
- using Jint.Runtime;
- using Jint.Runtime.Descriptors;
- using Jint.Runtime.Interop;
- namespace Jint.Native.Number;
- /// <summary>
- /// https://tc39.es/ecma262/#sec-properties-of-the-number-prototype-object
- /// </summary>
- internal sealed class NumberPrototype : NumberInstance
- {
- private const int SmallDtoaLength = FastDtoa.KFastDtoaMaximalLength + 8;
- private const int LargeDtoaLength = 101;
- private readonly Realm _realm;
- private readonly NumberConstructor _constructor;
- internal NumberPrototype(
- Engine engine,
- Realm realm,
- NumberConstructor constructor,
- ObjectPrototype objectPrototype)
- : base(engine, InternalTypes.Object | InternalTypes.PlainObject)
- {
- _prototype = objectPrototype;
- _realm = realm;
- _constructor = constructor;
- }
- protected override void Initialize()
- {
- var properties = new PropertyDictionary(8, checkExistingKeys: false)
- {
- ["constructor"] = new PropertyDescriptor(_constructor, true, false, true),
- ["toString"] = new PropertyDescriptor(new ClrFunction(Engine, "toString", ToNumberString, 1, PropertyFlag.Configurable), true, false, true),
- ["toLocaleString"] = new PropertyDescriptor(new ClrFunction(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), true, false, true),
- ["valueOf"] = new PropertyDescriptor(new ClrFunction(Engine, "valueOf", ValueOf, 0, PropertyFlag.Configurable), true, false, true),
- ["toFixed"] = new PropertyDescriptor(new ClrFunction(Engine, "toFixed", ToFixed, 1, PropertyFlag.Configurable), true, false, true),
- ["toExponential"] = new PropertyDescriptor(new ClrFunction(Engine, "toExponential", ToExponential, 1, PropertyFlag.Configurable), true, false, true),
- ["toPrecision"] = new PropertyDescriptor(new ClrFunction(Engine, "toPrecision", ToPrecision, 1, PropertyFlag.Configurable), true, false, true)
- };
- SetProperties(properties);
- }
- /// <summary>
- /// https://tc39.es/ecma262/#sec-number.prototype.tolocalestring
- /// </summary>
- private JsValue ToLocaleString(JsValue thisObject, JsCallArguments arguments)
- {
- if (!thisObject.IsNumber() && thisObject is not NumberInstance)
- {
- Throw.TypeError(_realm);
- }
- var m = TypeConverter.ToNumber(thisObject);
- if (double.IsNaN(m))
- {
- return "NaN";
- }
- if (m == 0)
- {
- return JsString.NumberZeroString;
- }
- if (m < 0)
- {
- return "-" + ToLocaleString(-m, arguments);
- }
- if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
- {
- return "Infinity";
- }
- if (double.IsNegativeInfinity(m) || m <= -double.MaxValue)
- {
- return "-Infinity";
- }
- var numberFormat = (NumberFormatInfo) Engine.Options.Culture.NumberFormat.Clone();
- try
- {
- if (arguments.Length > 0 && arguments[0].IsString())
- {
- var cultureArgument = arguments[0].ToString();
- numberFormat = (NumberFormatInfo) CultureInfo.GetCultureInfo(cultureArgument).NumberFormat.Clone();
- }
- int decDigitCount = NumberIntlHelper.GetDecimalDigitCount(m);
- numberFormat.NumberDecimalDigits = decDigitCount;
- }
- catch (CultureNotFoundException)
- {
- Throw.RangeError(_realm, "Incorrect locale information provided");
- }
- return m.ToString("n", numberFormat);
- }
- private JsValue ValueOf(JsValue thisObject, JsCallArguments arguments)
- {
- if (thisObject is NumberInstance ni)
- {
- return ni.NumberData;
- }
- if (thisObject is JsNumber)
- {
- return thisObject;
- }
- Throw.TypeError(_realm);
- return null;
- }
- private const double Ten21 = 1e21;
- private JsValue ToFixed(JsValue thisObject, JsCallArguments arguments)
- {
- var f = (int) TypeConverter.ToInteger(arguments.At(0, 0));
- if (f < 0 || f > 100)
- {
- Throw.RangeError(_realm, "fractionDigits argument must be between 0 and 100");
- }
- // limitation with .NET, max is 99
- if (f == 100)
- {
- Throw.RangeError(_realm, "100 fraction digits is not supported due to .NET format specifier limitation");
- }
- var x = TypeConverter.ToNumber(thisObject);
- if (double.IsNaN(x))
- {
- return "NaN";
- }
- if (x >= Ten21)
- {
- 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);
- }
- /// <summary>
- /// https://www.ecma-international.org/ecma-262/6.0/#sec-number.prototype.toexponential
- /// </summary>
- private JsValue ToExponential(JsValue thisObject, JsCallArguments arguments)
- {
- if (!thisObject.IsNumber() && ReferenceEquals(thisObject.TryCast<NumberInstance>(), null))
- {
- Throw.TypeError(_realm);
- }
- var x = TypeConverter.ToNumber(thisObject);
- var fractionDigits = arguments.At(0);
- if (fractionDigits.IsUndefined())
- {
- fractionDigits = JsNumber.PositiveZero;
- }
- var f = (int) TypeConverter.ToInteger(fractionDigits);
- if (double.IsNaN(x))
- {
- return "NaN";
- }
- if (double.IsInfinity(x))
- {
- return thisObject.ToString();
- }
- if (f < 0 || f > 100)
- {
- Throw.RangeError(_realm, "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(stackalloc char[f == -1 ? SmallDtoaLength : LargeDtoaLength]);
- if (f == -1)
- {
- DtoaNumberFormatter.DoubleToAscii(
- ref dtoaBuilder,
- x,
- DtoaMode.Shortest,
- requested_digits: 0,
- out _,
- out decimalPoint);
- f = dtoaBuilder.Length - 1;
- }
- else
- {
- DtoaNumberFormatter.DoubleToAscii(
- ref 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(ref dtoaBuilder, exponent, negative, f + 1);
- return result;
- }
- private JsValue ToPrecision(JsValue thisObject, JsCallArguments arguments)
- {
- if (!thisObject.IsNumber() && ReferenceEquals(thisObject.TryCast<NumberInstance>(), null))
- {
- Throw.TypeError(_realm);
- }
- var x = TypeConverter.ToNumber(thisObject);
- var precisionArgument = arguments.At(0);
- if (precisionArgument.IsUndefined())
- {
- return TypeConverter.ToString(x);
- }
- var p = (int) TypeConverter.ToInteger(precisionArgument);
- if (double.IsNaN(x))
- {
- return "NaN";
- }
- if (double.IsInfinity(x))
- {
- return thisObject.ToString();
- }
- if (p < 1 || p > 100)
- {
- Throw.RangeError(_realm, "precision must be between 1 and 100");
- }
- var dtoaBuilder = new DtoaBuilder(stackalloc char[LargeDtoaLength]);
- DtoaNumberFormatter.DoubleToAscii(
- ref dtoaBuilder,
- x,
- DtoaMode.Precision,
- p,
- out var negative,
- out var decimalPoint);
- int exponent = decimalPoint - 1;
- if (exponent < -6 || exponent >= p)
- {
- return CreateExponentialRepresentation(ref dtoaBuilder, exponent, negative, p);
- }
- var sb = new ValueStringBuilder(stackalloc char[128]);
- // Use fixed notation.
- if (negative)
- {
- sb.Append('-');
- }
- if (decimalPoint <= 0)
- {
- sb.Append("0.");
- sb.Append('0', -decimalPoint);
- sb.Append(dtoaBuilder._chars.Slice(0, dtoaBuilder.Length));
- sb.Append('0', p - dtoaBuilder.Length);
- }
- else
- {
- int m = System.Math.Min(dtoaBuilder.Length, decimalPoint);
- sb.Append(dtoaBuilder._chars.Slice(0, m));
- sb.Append('0', System.Math.Max(0, decimalPoint - dtoaBuilder.Length));
- if (decimalPoint < p)
- {
- sb.Append('.');
- var extra = negative ? 2 : 1;
- if (dtoaBuilder.Length > decimalPoint)
- {
- int len = dtoaBuilder.Length - decimalPoint;
- int n = System.Math.Min(len, p - (sb.Length - extra));
- sb.Append(dtoaBuilder._chars.Slice(decimalPoint, n));
- }
- sb.Append('0', System.Math.Max(0, extra + (p - sb.Length)));
- }
- }
- return sb.ToString();
- }
- private static string CreateExponentialRepresentation(
- ref DtoaBuilder buffer,
- int exponent,
- bool negative,
- int significantDigits)
- {
- bool negativeExponent = false;
- if (exponent < 0)
- {
- negativeExponent = true;
- exponent = -exponent;
- }
- var sb = new ValueStringBuilder(stackalloc char[128]);
- if (negative)
- {
- sb.Append('-');
- }
- sb.Append(buffer[0]);
- if (significantDigits != 1)
- {
- sb.Append('.');
- sb.Append(buffer.Slice(1, buffer.Length - 1));
- int length = buffer.Length;
- sb.Append('0', significantDigits - length);
- }
- sb.Append('e');
- sb.Append(negativeExponent ? '-' : '+');
- sb.Append(exponent);
- return sb.ToString();
- }
- private JsValue ToNumberString(JsValue thisObject, JsCallArguments arguments)
- {
- if (!thisObject.IsNumber() && (ReferenceEquals(thisObject.TryCast<NumberInstance>(), null)))
- {
- Throw.TypeError(_realm);
- }
- var radix = arguments.At(0).IsUndefined()
- ? 10
- : (int) TypeConverter.ToInteger(arguments.At(0));
- if (radix < 2 || radix > 36)
- {
- Throw.RangeError(_realm, "radix must be between 2 and 36");
- }
- var x = TypeConverter.ToNumber(thisObject);
- if (double.IsNaN(x))
- {
- return "NaN";
- }
- if (x == 0)
- {
- return JsString.NumberZeroString;
- }
- if (double.IsPositiveInfinity(x) || x >= double.MaxValue)
- {
- return "Infinity";
- }
- if (x < 0)
- {
- return "-" + ToNumberString(-x, arguments);
- }
- if (radix == 10)
- {
- return ToNumberString(x);
- }
- var integer = (long) x;
- var fraction = x - integer;
- string result = NumberPrototype.ToBase(integer, radix);
- if (fraction != 0)
- {
- result += "." + NumberPrototype.ToFractionBase(fraction, radix);
- }
- return result;
- }
- internal static string ToBase(long n, int radix)
- {
- const string Digits = "0123456789abcdefghijklmnopqrstuvwxyz";
- if (n == 0)
- {
- return "0";
- }
- var sb = new ValueStringBuilder(stackalloc char[64]);
- while (n > 0)
- {
- var digit = (int) (n % radix);
- n /= radix;
- sb.Append(Digits[digit]);
- }
- sb.Reverse();
- return sb.ToString();
- }
- internal 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 == 0)
- {
- return "0";
- }
- var result = new ValueStringBuilder(stackalloc char[64]);
- while (n > 0 && result.Length < 50) // arbitrary limit
- {
- var c = n * radix;
- var d = (int) c;
- n = c - d;
- result.Append(Digits[d]);
- }
- return result.ToString();
- }
- internal static string ToNumberString(double m)
- {
- if (double.IsNaN(m))
- {
- return "NaN";
- }
- if (m == 0)
- {
- return "0";
- }
- if (double.IsInfinity(m))
- {
- return double.IsNegativeInfinity(m) ? "-Infinity" : "Infinity";
- }
- var builder = new DtoaBuilder(stackalloc char[SmallDtoaLength]);
- DtoaNumberFormatter.DoubleToAscii(
- ref builder,
- m,
- DtoaMode.Shortest,
- 0,
- out var negative,
- out var decimal_point);
- var stringBuilder = new ValueStringBuilder(stackalloc char[64]);
- if (negative)
- {
- stringBuilder.Append('-');
- }
- if (builder.Length <= decimal_point && decimal_point <= 21)
- {
- // ECMA-262 section 9.8.1 step 6.
- stringBuilder.Append(builder._chars.Slice(0, builder.Length));
- stringBuilder.Append('0', decimal_point - builder.Length);
- }
- else if (0 < decimal_point && decimal_point <= 21)
- {
- // ECMA-262 section 9.8.1 step 7.
- stringBuilder.Append(builder._chars.Slice(0, decimal_point));
- stringBuilder.Append('.');
- stringBuilder.Append(builder._chars.Slice(decimal_point, builder.Length - decimal_point));
- }
- else if (decimal_point <= 0 && decimal_point > -6)
- {
- // ECMA-262 section 9.8.1 step 8.
- stringBuilder.Append("0.");
- stringBuilder.Append('0', -decimal_point);
- stringBuilder.Append(builder._chars.Slice(0, builder.Length));
- }
- else
- {
- // 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.Slice(1, builder.Length - 1));
- }
- stringBuilder.Append('e');
- stringBuilder.Append((decimal_point >= 0) ? '+' : '-');
- int exponent = decimal_point - 1;
- if (exponent < 0)
- {
- exponent = -exponent;
- }
- stringBuilder.Append(exponent);
- }
- return stringBuilder.ToString();
- }
- }
|