NumberPrototype.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. using System.Diagnostics;
  2. using System.Globalization;
  3. using System.Text;
  4. using Jint.Collections;
  5. using Jint.Native.Number.Dtoa;
  6. using Jint.Native.Object;
  7. using Jint.Pooling;
  8. using Jint.Runtime;
  9. using Jint.Runtime.Descriptors;
  10. using Jint.Runtime.Interop;
  11. namespace Jint.Native.Number
  12. {
  13. /// <summary>
  14. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4
  15. /// </summary>
  16. public sealed class NumberPrototype : NumberInstance
  17. {
  18. private readonly Realm _realm;
  19. private readonly NumberConstructor _constructor;
  20. internal NumberPrototype(
  21. Engine engine,
  22. Realm realm,
  23. NumberConstructor constructor,
  24. ObjectPrototype objectPrototype)
  25. : base(engine)
  26. {
  27. _prototype = objectPrototype;
  28. NumberData = JsNumber.Create(0);
  29. _realm = realm;
  30. _constructor = constructor;
  31. }
  32. protected override void Initialize()
  33. {
  34. var properties = new PropertyDictionary(8, checkExistingKeys: false)
  35. {
  36. ["constructor"] = new PropertyDescriptor(_constructor, true, false, true),
  37. ["toString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toString", ToNumberString, 1, PropertyFlag.Configurable), true, false, true),
  38. ["toLocaleString"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), true, false, true),
  39. ["valueOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, PropertyFlag.Configurable), true, false, true),
  40. ["toFixed"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toFixed", ToFixed, 1, PropertyFlag.Configurable), true, false, true),
  41. ["toExponential"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toExponential", ToExponential, 1, PropertyFlag.Configurable), true, false, true),
  42. ["toPrecision"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "toPrecision", ToPrecision, 1, PropertyFlag.Configurable), true, false, true)
  43. };
  44. SetProperties(properties);
  45. }
  46. private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
  47. {
  48. if (!thisObject.IsNumber() && ReferenceEquals(thisObject.TryCast<NumberInstance>(), null))
  49. {
  50. ExceptionHelper.ThrowTypeError(_realm);
  51. }
  52. var m = TypeConverter.ToNumber(thisObject);
  53. if (double.IsNaN(m))
  54. {
  55. return "NaN";
  56. }
  57. if (m == 0)
  58. {
  59. return JsString.NumberZeroString;
  60. }
  61. if (m < 0)
  62. {
  63. return "-" + ToLocaleString(-m, arguments);
  64. }
  65. if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
  66. {
  67. return "Infinity";
  68. }
  69. if (double.IsNegativeInfinity(m) || m <= -double.MaxValue)
  70. {
  71. return "-Infinity";
  72. }
  73. return m.ToString("n", Engine.Options.Culture);
  74. }
  75. private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
  76. {
  77. if (thisObj is NumberInstance ni)
  78. {
  79. return ni.NumberData;
  80. }
  81. if (thisObj is JsNumber)
  82. {
  83. return thisObj;
  84. }
  85. ExceptionHelper.ThrowTypeError(_realm);
  86. return null;
  87. }
  88. private const double Ten21 = 1e21;
  89. private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
  90. {
  91. var f = (int) TypeConverter.ToInteger(arguments.At(0, 0));
  92. if (f < 0 || f > 100)
  93. {
  94. ExceptionHelper.ThrowRangeError(_realm, "fractionDigits argument must be between 0 and 100");
  95. }
  96. // limitation with .NET, max is 99
  97. if (f == 100)
  98. {
  99. ExceptionHelper.ThrowRangeError(_realm, "100 fraction digits is not supported due to .NET format specifier limitation");
  100. }
  101. var x = TypeConverter.ToNumber(thisObj);
  102. if (double.IsNaN(x))
  103. {
  104. return "NaN";
  105. }
  106. if (x >= Ten21)
  107. {
  108. return ToNumberString(x);
  109. }
  110. // handle non-decimal with greater precision
  111. if (System.Math.Abs(x - (long) x) < JsNumber.DoubleIsIntegerTolerance)
  112. {
  113. return ((long) x).ToString("f" + f, CultureInfo.InvariantCulture);
  114. }
  115. return x.ToString("f" + f, CultureInfo.InvariantCulture);
  116. }
  117. /// <summary>
  118. /// https://www.ecma-international.org/ecma-262/6.0/#sec-number.prototype.toexponential
  119. /// </summary>
  120. private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
  121. {
  122. if (!thisObj.IsNumber() && ReferenceEquals(thisObj.TryCast<NumberInstance>(), null))
  123. {
  124. ExceptionHelper.ThrowTypeError(_realm);
  125. }
  126. var x = TypeConverter.ToNumber(thisObj);
  127. var fractionDigits = arguments.At(0);
  128. if (fractionDigits.IsUndefined())
  129. {
  130. fractionDigits = JsNumber.PositiveZero;
  131. }
  132. var f = (int) TypeConverter.ToInteger(fractionDigits);
  133. if (double.IsNaN(x))
  134. {
  135. return "NaN";
  136. }
  137. if (double.IsInfinity(x))
  138. {
  139. return thisObj.ToString();
  140. }
  141. if (f < 0 || f > 100)
  142. {
  143. ExceptionHelper.ThrowRangeError(_realm, "fractionDigits argument must be between 0 and 100");
  144. }
  145. if (arguments.At(0).IsUndefined())
  146. {
  147. f = -1;
  148. }
  149. bool negative = false;
  150. if (x < 0)
  151. {
  152. x = -x;
  153. negative = true;
  154. }
  155. int decimalPoint;
  156. DtoaBuilder dtoaBuilder;
  157. if (f == -1)
  158. {
  159. dtoaBuilder = new DtoaBuilder();
  160. DtoaNumberFormatter.DoubleToAscii(
  161. dtoaBuilder,
  162. x,
  163. DtoaMode.Shortest,
  164. requested_digits: 0,
  165. out _,
  166. out decimalPoint);
  167. f = dtoaBuilder.Length - 1;
  168. }
  169. else
  170. {
  171. dtoaBuilder = new DtoaBuilder(101);
  172. DtoaNumberFormatter.DoubleToAscii(
  173. dtoaBuilder,
  174. x,
  175. DtoaMode.Precision,
  176. requested_digits: f + 1,
  177. out _,
  178. out decimalPoint);
  179. }
  180. Debug.Assert(dtoaBuilder.Length > 0);
  181. Debug.Assert(dtoaBuilder.Length <= f + 1);
  182. int exponent = decimalPoint - 1;
  183. var result = CreateExponentialRepresentation(dtoaBuilder, exponent, negative, f+1);
  184. return result;
  185. }
  186. private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)
  187. {
  188. if (!thisObj.IsNumber() && ReferenceEquals(thisObj.TryCast<NumberInstance>(), null))
  189. {
  190. ExceptionHelper.ThrowTypeError(_realm);
  191. }
  192. var x = TypeConverter.ToNumber(thisObj);
  193. var precisionArgument = arguments.At(0);
  194. if (precisionArgument.IsUndefined())
  195. {
  196. return TypeConverter.ToString(x);
  197. }
  198. var p = (int) TypeConverter.ToInteger(precisionArgument);
  199. if (double.IsNaN(x))
  200. {
  201. return "NaN";
  202. }
  203. if (double.IsInfinity(x))
  204. {
  205. return thisObj.ToString();
  206. }
  207. if (p < 1 || p > 100)
  208. {
  209. ExceptionHelper.ThrowRangeError(_realm, "precision must be between 1 and 100");
  210. }
  211. var dtoaBuilder = new DtoaBuilder(101);
  212. DtoaNumberFormatter.DoubleToAscii(
  213. dtoaBuilder,
  214. x,
  215. DtoaMode.Precision,
  216. p,
  217. out var negative,
  218. out var decimalPoint);
  219. int exponent = decimalPoint - 1;
  220. if (exponent < -6 || exponent >= p)
  221. {
  222. return CreateExponentialRepresentation(dtoaBuilder, exponent, negative, p);
  223. }
  224. using (var builder = StringBuilderPool.Rent())
  225. {
  226. // Use fixed notation.
  227. if (negative)
  228. {
  229. builder.Builder.Append('-');
  230. }
  231. if (decimalPoint <= 0)
  232. {
  233. builder.Builder.Append("0.");
  234. builder.Builder.Append('0', -decimalPoint);
  235. builder.Builder.Append(dtoaBuilder._chars, 0, dtoaBuilder.Length);
  236. builder.Builder.Append('0', p - dtoaBuilder.Length);
  237. }
  238. else
  239. {
  240. int m = System.Math.Min(dtoaBuilder.Length, decimalPoint);
  241. builder.Builder.Append(dtoaBuilder._chars, 0, m);
  242. builder.Builder.Append('0', System.Math.Max(0, decimalPoint - dtoaBuilder.Length));
  243. if (decimalPoint < p)
  244. {
  245. builder.Builder.Append('.');
  246. var extra = negative ? 2 : 1;
  247. if (dtoaBuilder.Length > decimalPoint)
  248. {
  249. int len = dtoaBuilder.Length - decimalPoint;
  250. int n = System.Math.Min(len, p - (builder.Builder.Length - extra));
  251. builder.Builder.Append(dtoaBuilder._chars, decimalPoint, n);
  252. }
  253. builder.Builder.Append('0', System.Math.Max(0, extra + (p - builder.Builder.Length)));
  254. }
  255. }
  256. return builder.ToString();
  257. }
  258. }
  259. private string CreateExponentialRepresentation(
  260. DtoaBuilder buffer,
  261. int exponent,
  262. bool negative,
  263. int significantDigits)
  264. {
  265. bool negativeExponent = false;
  266. if (exponent < 0)
  267. {
  268. negativeExponent = true;
  269. exponent = -exponent;
  270. }
  271. using (var builder = StringBuilderPool.Rent())
  272. {
  273. if (negative)
  274. {
  275. builder.Builder.Append('-');
  276. }
  277. builder.Builder.Append(buffer._chars[0]);
  278. if (significantDigits != 1)
  279. {
  280. builder.Builder.Append('.');
  281. builder.Builder.Append(buffer._chars, 1, buffer.Length - 1);
  282. int length = buffer.Length;
  283. builder.Builder.Append('0', significantDigits - length);
  284. }
  285. builder.Builder.Append('e');
  286. builder.Builder.Append(negativeExponent ? '-' : '+');
  287. builder.Builder.Append(exponent);
  288. return builder.ToString();
  289. }
  290. }
  291. private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments)
  292. {
  293. if (!thisObject.IsNumber() && (ReferenceEquals(thisObject.TryCast<NumberInstance>(), null)))
  294. {
  295. ExceptionHelper.ThrowTypeError(_realm);
  296. }
  297. var radix = arguments.At(0).IsUndefined()
  298. ? 10
  299. : (int) TypeConverter.ToInteger(arguments.At(0));
  300. if (radix < 2 || radix > 36)
  301. {
  302. ExceptionHelper.ThrowRangeError(_realm, "radix must be between 2 and 36");
  303. }
  304. var x = TypeConverter.ToNumber(thisObject);
  305. if (double.IsNaN(x))
  306. {
  307. return "NaN";
  308. }
  309. if (x == 0)
  310. {
  311. return JsString.NumberZeroString;
  312. }
  313. if (double.IsPositiveInfinity(x) || x >= double.MaxValue)
  314. {
  315. return "Infinity";
  316. }
  317. if (x < 0)
  318. {
  319. return "-" + ToNumberString(-x, arguments);
  320. }
  321. if (radix == 10)
  322. {
  323. return ToNumberString(x);
  324. }
  325. var integer = (long) x;
  326. var fraction = x - integer;
  327. string result = ToBase(integer, radix);
  328. if (fraction != 0)
  329. {
  330. result += "." + ToFractionBase(fraction, radix);
  331. }
  332. return result;
  333. }
  334. public string ToBase(long n, int radix)
  335. {
  336. const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
  337. if (n == 0)
  338. {
  339. return "0";
  340. }
  341. using (var result = StringBuilderPool.Rent())
  342. {
  343. while (n > 0)
  344. {
  345. var digit = (int) (n % radix);
  346. n = n / radix;
  347. result.Builder.Insert(0, digits[digit]);
  348. }
  349. return result.ToString();
  350. }
  351. }
  352. public string ToFractionBase(double n, int radix)
  353. {
  354. // based on the repeated multiplication method
  355. // http://www.mathpath.org/concepts/Num/frac.htm
  356. const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
  357. if (n == 0)
  358. {
  359. return "0";
  360. }
  361. using (var result = StringBuilderPool.Rent())
  362. {
  363. while (n > 0 && result.Length < 50) // arbitrary limit
  364. {
  365. var c = n*radix;
  366. var d = (int) c;
  367. n = c - d;
  368. result.Builder.Append(digits[d]);
  369. }
  370. return result.ToString();
  371. }
  372. }
  373. private string ToNumberString(double m)
  374. {
  375. using (var stringBuilder = StringBuilderPool.Rent())
  376. {
  377. return NumberToString(m, new DtoaBuilder(), stringBuilder.Builder);
  378. }
  379. }
  380. internal static string NumberToString(
  381. double m,
  382. DtoaBuilder builder,
  383. StringBuilder stringBuilder)
  384. {
  385. if (double.IsNaN(m))
  386. {
  387. return "NaN";
  388. }
  389. if (m == 0)
  390. {
  391. return "0";
  392. }
  393. if (double.IsPositiveInfinity(m))
  394. {
  395. return "Infinity";
  396. }
  397. if (double.IsNegativeInfinity(m))
  398. {
  399. return "-Infinity";
  400. }
  401. DtoaNumberFormatter.DoubleToAscii(
  402. builder,
  403. m,
  404. DtoaMode.Shortest,
  405. 0,
  406. out var negative,
  407. out var decimal_point);
  408. if (negative)
  409. {
  410. stringBuilder.Append('-');
  411. }
  412. if (builder.Length <= decimal_point && decimal_point <= 21)
  413. {
  414. // ECMA-262 section 9.8.1 step 6.
  415. stringBuilder.Append(builder._chars, 0, builder.Length);
  416. stringBuilder.Append('0', decimal_point - builder.Length);
  417. }
  418. else if (0 < decimal_point && decimal_point <= 21)
  419. {
  420. // ECMA-262 section 9.8.1 step 7.
  421. stringBuilder.Append(builder._chars, 0, decimal_point);
  422. stringBuilder.Append('.');
  423. stringBuilder.Append(builder._chars, decimal_point, builder.Length - decimal_point);
  424. }
  425. else if (decimal_point <= 0 && decimal_point > -6)
  426. {
  427. // ECMA-262 section 9.8.1 step 8.
  428. stringBuilder.Append("0.");
  429. stringBuilder.Append('0', -decimal_point);
  430. stringBuilder.Append(builder._chars, 0, builder.Length);
  431. }
  432. else
  433. {
  434. // ECMA-262 section 9.8.1 step 9 and 10 combined.
  435. stringBuilder.Append(builder._chars[0]);
  436. if (builder.Length != 1)
  437. {
  438. stringBuilder.Append('.');
  439. stringBuilder.Append(builder._chars, 1, builder.Length - 1);
  440. }
  441. stringBuilder.Append('e');
  442. stringBuilder.Append((decimal_point >= 0) ? '+' : '-');
  443. int exponent = decimal_point - 1;
  444. if (exponent < 0)
  445. {
  446. exponent = -exponent;
  447. }
  448. stringBuilder.Append(exponent);
  449. }
  450. return stringBuilder.ToString();
  451. }
  452. }
  453. }