NumberPrototype.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. using System;
  2. using System.Globalization;
  3. using System.Text;
  4. using Jint.Native.Number.Dtoa;
  5. using Jint.Runtime;
  6. using Jint.Runtime.Interop;
  7. namespace Jint.Native.Number
  8. {
  9. /// <summary>
  10. /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4
  11. /// </summary>
  12. public sealed class NumberPrototype : NumberInstance
  13. {
  14. private NumberPrototype(Engine engine)
  15. : base(engine)
  16. {
  17. }
  18. public static NumberPrototype CreatePrototypeObject(Engine engine, NumberConstructor numberConstructor)
  19. {
  20. var obj = new NumberPrototype(engine);
  21. obj.Prototype = engine.Object.PrototypeObject;
  22. obj.PrimitiveValue = 0;
  23. obj.Extensible = true;
  24. obj.FastAddProperty("constructor", numberConstructor, true, false, true);
  25. return obj;
  26. }
  27. public void Configure()
  28. {
  29. FastAddProperty("toString", new ClrFunctionInstance(Engine, ToNumberString), true, false, true);
  30. FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, ToLocaleString), true, false, true);
  31. FastAddProperty("valueOf", new ClrFunctionInstance(Engine, ValueOf), true, false, true);
  32. FastAddProperty("toFixed", new ClrFunctionInstance(Engine, ToFixed, 1), true, false, true);
  33. FastAddProperty("toExponential", new ClrFunctionInstance(Engine, ToExponential), true, false, true);
  34. FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, ToPrecision), true, false, true);
  35. }
  36. private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
  37. {
  38. if (!thisObject.IsNumber() && (thisObject.TryCast<NumberInstance>() == null))
  39. {
  40. throw new JavaScriptException(Engine.TypeError);
  41. }
  42. var m = TypeConverter.ToNumber(thisObject);
  43. if (double.IsNaN(m))
  44. {
  45. return "NaN";
  46. }
  47. if (m.Equals(0))
  48. {
  49. return "0";
  50. }
  51. if (m < 0)
  52. {
  53. return "-" + ToLocaleString(-m, arguments);
  54. }
  55. if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
  56. {
  57. return "Infinity";
  58. }
  59. if (double.IsNegativeInfinity(m) || m <= -double.MaxValue)
  60. {
  61. return "-Infinity";
  62. }
  63. return m.ToString("n", Engine.Options._Culture);
  64. }
  65. private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
  66. {
  67. var number = thisObj.TryCast<NumberInstance>();
  68. if (number == null)
  69. {
  70. throw new JavaScriptException(Engine.TypeError);
  71. }
  72. return number.PrimitiveValue;
  73. }
  74. private const double Ten21 = 1e21;
  75. private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
  76. {
  77. var f = (int)TypeConverter.ToInteger(arguments.At(0, 0));
  78. if (f < 0 || f > 20)
  79. {
  80. throw new JavaScriptException(Engine.RangeError, "fractionDigits argument must be between 0 and 20");
  81. }
  82. var x = TypeConverter.ToNumber(thisObj);
  83. if (double.IsNaN(x))
  84. {
  85. return "NaN";
  86. }
  87. if (x >= Ten21)
  88. {
  89. return ToNumberString(x);
  90. }
  91. return x.ToString("f" + f, CultureInfo.InvariantCulture);
  92. }
  93. private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
  94. {
  95. var f = (int)TypeConverter.ToInteger(arguments.At(0, 16));
  96. if (f < 0 || f > 20)
  97. {
  98. throw new JavaScriptException(Engine.RangeError, "fractionDigits argument must be between 0 and 20");
  99. }
  100. var x = TypeConverter.ToNumber(thisObj);
  101. if (double.IsNaN(x))
  102. {
  103. return "NaN";
  104. }
  105. string format = System.String.Concat("#.", new System.String('0', f), "e+0");
  106. return x.ToString(format, CultureInfo.InvariantCulture);
  107. }
  108. private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)
  109. {
  110. var x = TypeConverter.ToNumber(thisObj);
  111. if (arguments.At(0) == Undefined.Instance)
  112. {
  113. return TypeConverter.ToString(x);
  114. }
  115. var p = TypeConverter.ToInteger(arguments.At(0));
  116. if (double.IsInfinity(x) || double.IsNaN(x))
  117. {
  118. return TypeConverter.ToString(x);
  119. }
  120. if (p < 1 || p > 21)
  121. {
  122. throw new JavaScriptException(Engine.RangeError, "precision must be between 1 and 21");
  123. }
  124. // Get the number of decimals
  125. string str = x.ToString("e23", CultureInfo.InvariantCulture);
  126. int decimals = str.IndexOfAny(new [] { '.', 'e' });
  127. decimals = decimals == -1 ? str.Length : decimals;
  128. p -= decimals;
  129. p = p < 1 ? 1 : p;
  130. return x.ToString("f" + p, CultureInfo.InvariantCulture);
  131. }
  132. private JsValue ToNumberString(JsValue thisObject, JsValue[] arguments)
  133. {
  134. if (!thisObject.IsNumber() && (thisObject.TryCast<NumberInstance>() == null))
  135. {
  136. throw new JavaScriptException(Engine.TypeError);
  137. }
  138. var radix = arguments.At(0) == JsValue.Undefined ? 10 : (int) TypeConverter.ToInteger(arguments.At(0));
  139. if (radix < 2 || radix > 36)
  140. {
  141. throw new JavaScriptException(Engine.RangeError, "radix must be between 2 and 36");
  142. }
  143. var x = TypeConverter.ToNumber(thisObject);
  144. if (double.IsNaN(x))
  145. {
  146. return "NaN";
  147. }
  148. if (x.Equals(0))
  149. {
  150. return "0";
  151. }
  152. if (double.IsPositiveInfinity(x) || x >= double.MaxValue)
  153. {
  154. return "Infinity";
  155. }
  156. if (x < 0)
  157. {
  158. return "-" + ToNumberString(-x, arguments);
  159. }
  160. if (radix == 10)
  161. {
  162. return ToNumberString(x);
  163. }
  164. var integer = (long) x;
  165. var fraction = x - integer;
  166. string result = ToBase(integer, radix);
  167. if (!fraction.Equals(0))
  168. {
  169. result += "." + ToFractionBase(fraction, radix);
  170. }
  171. return result;
  172. }
  173. public static string ToBase(long n, int radix)
  174. {
  175. const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
  176. if (n == 0)
  177. {
  178. return "0";
  179. }
  180. var result = new StringBuilder();
  181. while (n > 0)
  182. {
  183. var digit = (int)(n % radix);
  184. n = n / radix;
  185. result.Insert(0, digits[digit].ToString());
  186. }
  187. return result.ToString();
  188. }
  189. public static string ToFractionBase(double n, int radix)
  190. {
  191. // based on the repeated multiplication method
  192. // http://www.mathpath.org/concepts/Num/frac.htm
  193. const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
  194. if (n.Equals(0))
  195. {
  196. return "0";
  197. }
  198. var result = new StringBuilder();
  199. while (n > 0 && result.Length < 50) // arbitrary limit
  200. {
  201. var c = n*radix;
  202. var d = (int) c;
  203. n = c - d;
  204. result.Append(digits[d].ToString());
  205. }
  206. return result.ToString();
  207. }
  208. public static string ToNumberString(double m)
  209. {
  210. if (double.IsNaN(m))
  211. {
  212. return "NaN";
  213. }
  214. if (m.Equals(0))
  215. {
  216. return "0";
  217. }
  218. if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
  219. {
  220. return "Infinity";
  221. }
  222. if (m < 0)
  223. {
  224. return "-" + ToNumberString(-m);
  225. }
  226. // V8 FastDtoa can't convert all numbers, so try it first but
  227. // fall back to old DToA in case it fails
  228. var result = FastDtoa.NumberToString(m);
  229. if (result != null)
  230. {
  231. return result;
  232. }
  233. // s is all digits (significand)
  234. // k number of digits of s
  235. // n total of digits in fraction s*10^n-k=m
  236. // 123.4 s=1234, k=4, n=3
  237. // 1234000 s = 1234, k=4, n=7
  238. string s = null;
  239. var rFormat = m.ToString("r");
  240. if (rFormat.IndexOf("e", StringComparison.OrdinalIgnoreCase) == -1)
  241. {
  242. s = rFormat.Replace(".", "").TrimStart('0').TrimEnd('0');
  243. }
  244. const string format = "0.00000000000000000e0";
  245. var parts = m.ToString(format, CultureInfo.InvariantCulture).Split('e');
  246. if (s == null)
  247. {
  248. s = parts[0].TrimEnd('0').Replace(".", "");
  249. }
  250. var n = int.Parse(parts[1]) + 1;
  251. var k = s.Length;
  252. if (k <= n && n <= 21)
  253. {
  254. return s + new string('0', n - k);
  255. }
  256. if (0 < n && n <= 21)
  257. {
  258. return s.Substring(0, n) + '.' + s.Substring(n);
  259. }
  260. if (-6 < n && n <= 0)
  261. {
  262. return "0." + new string('0', -n) + s;
  263. }
  264. if (k == 1)
  265. {
  266. return s + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
  267. }
  268. return s.Substring(0, 1) + "." + s.Substring(1) + "e" + (n - 1 < 0 ? "-" : "+") + System.Math.Abs(n - 1);
  269. }
  270. }
  271. }