using System;
using System.Globalization;
using System.Text;
using Jint.Runtime;
using Jint.Runtime.Interop;
namespace Jint.Native.Number
{
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4
///
public sealed class NumberPrototype : NumberInstance
{
private NumberPrototype(Engine engine)
: base(engine)
{
}
public static NumberPrototype CreatePrototypeObject(Engine engine, NumberConstructor numberConstructor)
{
var obj = new NumberPrototype(engine);
obj.Prototype = engine.Object.PrototypeObject;
obj.PrimitiveValue = 0;
obj.Extensible = true;
obj.FastAddProperty("constructor", numberConstructor, true, false, true);
return obj;
}
public void Configure()
{
FastAddProperty("toString", new ClrFunctionInstance(Engine, ToNumberString), true, false, true);
FastAddProperty("toLocaleString", new ClrFunctionInstance(Engine, ToLocaleString), true, false, true);
FastAddProperty("valueOf", new ClrFunctionInstance(Engine, ValueOf), true, false, true);
FastAddProperty("toFixed", new ClrFunctionInstance(Engine, ToFixed, 1), true, false, true);
FastAddProperty("toExponential", new ClrFunctionInstance(Engine, ToExponential), true, false, true);
FastAddProperty("toPrecision", new ClrFunctionInstance(Engine, ToPrecision), true, false, true);
}
private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
{
if (!thisObject.IsNumber() && (thisObject.TryCast() == 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)
{
var number = thisObj.TryCast();
if (number == null)
{
throw new JavaScriptException(Engine.TypeError);
}
return number.PrimitiveValue;
}
private const double Ten21 = 1e21;
private JsValue ToFixed(JsValue thisObj, JsValue[] arguments)
{
var f = (int)TypeConverter.ToInteger(arguments.At(0, 0));
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";
}
if (x >= Ten21)
{
return ToNumberString(x);
}
var l = (long) x; // extract integer part
if (f == 0)
{
return l.ToString(CultureInfo.InvariantCulture);
}
var d = x - l;
return l.ToString(CultureInfo.InvariantCulture) + d.ToString("." + new string('0', f), CultureInfo.InvariantCulture);
}
private JsValue ToExponential(JsValue thisObj, JsValue[] arguments)
{
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)
{
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)
{
if (!thisObject.IsNumber() && (thisObject.TryCast() == null))
{
throw new JavaScriptException(Engine.TypeError);
}
var radix = arguments.At(0) == JsValue.Undefined ? 10 : (int) TypeConverter.ToInteger(arguments.At(0));
if (radix < 2 || radix > 36)
{
throw new JavaScriptException(Engine.RangeError, "radix must be between 2 and 36");
}
var x = TypeConverter.ToNumber(thisObject);
if (double.IsNaN(x))
{
return "NaN";
}
if (x.Equals(0))
{
return "0";
}
if (double.IsPositiveInfinity(x) || x >= double.MaxValue)
{
return "Infinity";
}
if (x < 0)
{
return "-" + ToNumberString(-x, arguments);
}
if (radix == 10)
{
return ToNumberString(x);
}
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))
{
result += "." + ToFractionBase(fraction, radix);
}
return result;
}
public static string ToBase(long n, int radix)
{
const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
if (n == 0)
{
return "0";
}
var result = new StringBuilder();
while (n > 0)
{
var digit = (int)n % radix;
n = n / radix;
result.Insert(0, digits[digit].ToString());
}
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";
}
var result = new StringBuilder();
while (n > 0 && result.Length < 50) // arbitrary limit
{
var c = n*radix;
var d = (int) c;
n = c - d;
result.Append(digits[d].ToString());
}
return result.ToString();
}
public static string ToNumberString(double m)
{
if (double.IsNaN(m))
{
return "NaN";
}
if (m.Equals(0))
{
return "0";
}
if (double.IsPositiveInfinity(m) || m >= double.MaxValue)
{
return "Infinity";
}
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(".", "");
}
var n = int.Parse(parts[1]) + 1;
var k = s.Length;
if (k <= n && n <= 21)
{
return s + new string('0', n - k);
}
if (0 < n && n <= 21)
{
return s.Substring(0, n) + '.' + s.Substring(n);
}
if (-6 < n && n <= 0)
{
return "0." + new string('0', -n) + s;
}
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);
}
}
}