NumberPrototype.cs 10 KB

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