2 * Copyright (C) 1999-2000,2003 Harri Porten (porten@kde.org)
3 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
23 #include "NumberPrototype.h"
25 #include "FunctionPrototype.h"
26 #include "JSFunction.h"
27 #include "ObjectPrototype.h"
29 #include "operations.h"
30 #include <wtf/Assertions.h>
31 #include <wtf/MathExtras.h>
32 #include <wtf/Vector.h>
37 static JSValue* numberProtoFuncToString(ExecState*, JSObject*, JSValue*, const ArgList&);
38 static JSValue* numberProtoFuncToLocaleString(ExecState*, JSObject*, JSValue*, const ArgList&);
39 static JSValue* numberProtoFuncValueOf(ExecState*, JSObject*, JSValue*, const ArgList&);
40 static JSValue* numberProtoFuncToFixed(ExecState*, JSObject*, JSValue*, const ArgList&);
41 static JSValue* numberProtoFuncToExponential(ExecState*, JSObject*, JSValue*, const ArgList&);
42 static JSValue* numberProtoFuncToPrecision(ExecState*, JSObject*, JSValue*, const ArgList&);
46 NumberPrototype::NumberPrototype(ExecState* exec, ObjectPrototype* objectPrototype, FunctionPrototype* functionPrototype)
47 : NumberObject(objectPrototype)
49 setInternalValue(jsNumber(exec, 0));
51 // The constructor will be added later, after NumberConstructor has been constructed
53 putDirectFunction(new (exec) PrototypeFunction(exec, functionPrototype, 1, exec->propertyNames().toString, numberProtoFuncToString), DontEnum);
54 putDirectFunction(new (exec) PrototypeFunction(exec, functionPrototype, 0, exec->propertyNames().toLocaleString, numberProtoFuncToLocaleString), DontEnum);
55 putDirectFunction(new (exec) PrototypeFunction(exec, functionPrototype, 0, exec->propertyNames().valueOf, numberProtoFuncValueOf), DontEnum);
56 putDirectFunction(new (exec) PrototypeFunction(exec, functionPrototype, 1, exec->propertyNames().toFixed, numberProtoFuncToFixed), DontEnum);
57 putDirectFunction(new (exec) PrototypeFunction(exec, functionPrototype, 1, exec->propertyNames().toExponential, numberProtoFuncToExponential), DontEnum);
58 putDirectFunction(new (exec) PrototypeFunction(exec, functionPrototype, 1, exec->propertyNames().toPrecision, numberProtoFuncToPrecision), DontEnum);
61 // ------------------------------ Functions ---------------------------
63 // ECMA 15.7.4.2 - 15.7.4.7
65 static UString integer_part_noexp(double d)
69 char* result = dtoa(d, 0, &decimalPoint, &sign, NULL);
70 bool resultIsInfOrNan = (decimalPoint == 9999);
71 size_t length = strlen(result);
73 UString str = sign ? "-" : "";
76 else if (decimalPoint <= 0)
79 Vector<char, 1024> buf(decimalPoint + 1);
81 if (static_cast<int>(length) <= decimalPoint) {
82 strcpy(buf.data(), result);
83 memset(buf.data() + length, '0', decimalPoint - length);
85 strncpy(buf.data(), result, decimalPoint);
87 buf[decimalPoint] = '\0';
88 str.append(buf.data());
96 static UString char_sequence(char c, int count)
98 Vector<char, 2048> buf(count + 1, c);
101 return UString(buf.data());
104 static double intPow10(int e)
106 // This function uses the "exponentiation by squaring" algorithm and
107 // long double to quickly and precisely calculate integer powers of 10.0.
109 // This is a handy workaround for <rdar://problem/4494756>
114 bool negative = e < 0;
115 unsigned exp = negative ? -e : e;
117 long double result = 10.0;
118 bool foundOne = false;
119 for (int bit = 31; bit >= 0; bit--) {
121 if ((exp >> bit) & 1)
124 result = result * result;
125 if ((exp >> bit) & 1)
126 result = result * 10.0;
131 return static_cast<double>(1.0 / result);
132 return static_cast<double>(result);
136 JSValue* numberProtoFuncToString(ExecState* exec, JSObject*, JSValue* thisValue, const ArgList& args)
138 JSValue* v = thisValue->getJSNumber();
140 return throwError(exec, TypeError);
142 double radixAsDouble = args[0]->toInteger(exec); // nan -> 0
143 if (radixAsDouble == 10 || args[0]->isUndefined())
144 return jsString(exec, v->toString(exec));
146 if (radixAsDouble < 2 || radixAsDouble > 36)
147 return throwError(exec, RangeError, "toString() radix argument must be between 2 and 36");
149 int radix = static_cast<int>(radixAsDouble);
150 const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
151 // INT_MAX results in 1024 characters left of the dot with radix 2
152 // give the same space on the right side. safety checks are in place
153 // unless someone finds a precise rule.
155 const char* lastCharInString = s + sizeof(s) - 1;
156 double x = v->uncheckedGetNumber();
157 if (isnan(x) || isinf(x))
158 return jsString(exec, UString::from(x));
160 bool isNegative = x < 0.0;
164 double integerPart = floor(x);
165 char* decimalPoint = s + sizeof(s) / 2;
167 // convert integer portion
168 char* p = decimalPoint;
169 double d = integerPart;
171 int remainderDigit = static_cast<int>(fmod(d, radix));
172 *--p = digits[remainderDigit];
174 } while ((d <= -1.0 || d >= 1.0) && s < p);
178 char* startOfResultString = p;
179 ASSERT(s <= startOfResultString);
183 const double epsilon = 0.001; // TODO: guessed. base on radix ?
184 bool hasFractionalPart = (d < -epsilon || d > epsilon);
185 if (hasFractionalPart) {
189 const int digit = static_cast<int>(d);
190 *p++ = digits[digit];
192 } while ((d < -epsilon || d > epsilon) && p < lastCharInString);
195 ASSERT(p < s + sizeof(s));
197 return jsString(exec, startOfResultString);
200 JSValue* numberProtoFuncToLocaleString(ExecState* exec, JSObject*, JSValue* thisValue, const ArgList&)
202 // FIXME: Not implemented yet.
204 JSValue* v = thisValue->getJSNumber();
206 return throwError(exec, TypeError);
208 return jsString(exec, v->toString(exec));
211 JSValue* numberProtoFuncValueOf(ExecState* exec, JSObject*, JSValue* thisValue, const ArgList&)
213 JSValue* v = thisValue->getJSNumber();
215 return throwError(exec, TypeError);
220 JSValue* numberProtoFuncToFixed(ExecState* exec, JSObject*, JSValue* thisValue, const ArgList& args)
222 JSValue* v = thisValue->getJSNumber();
224 return throwError(exec, TypeError);
226 JSValue* fractionDigits = args[0];
227 double df = fractionDigits->toInteger(exec);
228 if (!(df >= 0 && df <= 20))
229 return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20");
232 double x = v->uncheckedGetNumber();
234 return jsString(exec, "NaN");
240 } else if (x == -0.0)
243 if (x >= pow(10.0, 21.0))
244 return jsString(exec, s + UString::from(x));
246 const double tenToTheF = pow(10.0, f);
247 double n = floor(x * tenToTheF);
248 if (fabs(n / tenToTheF - x) >= fabs((n + 1) / tenToTheF - x))
251 UString m = integer_part_noexp(n);
256 for (int i = 0; i < f + 1 - k; i++)
260 ASSERT(k == m.size());
263 if (kMinusf < m.size())
264 return jsString(exec, s + m.substr(0, kMinusf) + "." + m.substr(kMinusf));
265 return jsString(exec, s + m.substr(0, kMinusf));
268 static void fractionalPartToString(char* buf, int& i, const char* result, int resultLength, int fractionalDigits)
270 if (fractionalDigits <= 0)
273 int fDigitsInResult = static_cast<int>(resultLength) - 1;
275 if (fDigitsInResult > 0) {
276 if (fractionalDigits < fDigitsInResult) {
277 strncpy(buf + i, result + 1, fractionalDigits);
278 i += fractionalDigits;
280 strcpy(buf + i, result + 1);
281 i += static_cast<int>(resultLength) - 1;
285 for (int j = 0; j < fractionalDigits - fDigitsInResult; j++)
289 static void exponentialPartToString(char* buf, int& i, int decimalPoint)
292 buf[i++] = (decimalPoint >= 0) ? '+' : '-';
293 // decimalPoint can't be more than 3 digits decimal given the
294 // nature of float representation
295 int exponential = decimalPoint - 1;
298 if (exponential >= 100)
299 buf[i++] = static_cast<char>('0' + exponential / 100);
300 if (exponential >= 10)
301 buf[i++] = static_cast<char>('0' + (exponential % 100) / 10);
302 buf[i++] = static_cast<char>('0' + exponential % 10);
305 JSValue* numberProtoFuncToExponential(ExecState* exec, JSObject*, JSValue* thisValue, const ArgList& args)
307 JSValue* v = thisValue->getJSNumber();
309 return throwError(exec, TypeError);
311 double x = v->uncheckedGetNumber();
313 if (isnan(x) || isinf(x))
314 return jsString(exec, UString::from(x));
316 JSValue* fractionalDigitsValue = args[0];
317 double df = fractionalDigitsValue->toInteger(exec);
318 if (!(df >= 0 && df <= 20))
319 return throwError(exec, RangeError, "toExponential() argument must between 0 and 20");
320 int fractionalDigits = (int)df;
321 bool includeAllDigits = fractionalDigitsValue->isUndefined();
323 int decimalAdjust = 0;
324 if (x && !includeAllDigits) {
325 double logx = floor(log10(fabs(x)));
326 x /= pow(10.0, logx);
327 const double tenToTheF = pow(10.0, fractionalDigits);
328 double fx = floor(x * tenToTheF) / tenToTheF;
329 double cx = ceil(x * tenToTheF) / tenToTheF;
331 if (fabs(fx - x) < fabs(cx - x))
336 decimalAdjust = static_cast<int>(logx);
340 return jsString(exec, "NaN");
342 if (x == -0.0) // (-0.0).toExponential() should print as 0 instead of -0
347 char* result = dtoa(x, 0, &decimalPoint, &sign, NULL);
348 size_t resultLength = strlen(result);
349 decimalPoint += decimalAdjust;
352 char buf[80]; // digit + '.' + fractionDigits (max 20) + 'e' + sign + exponent (max?)
356 if (decimalPoint == 999) // ? 9999 is the magical "result is Inf or NaN" value. what's 999??
357 strcpy(buf + i, result);
359 buf[i++] = result[0];
361 if (includeAllDigits)
362 fractionalDigits = static_cast<int>(resultLength) - 1;
364 fractionalPartToString(buf, i, result, resultLength, fractionalDigits);
365 exponentialPartToString(buf, i, decimalPoint);
372 return jsString(exec, buf);
375 JSValue* numberProtoFuncToPrecision(ExecState* exec, JSObject*, JSValue* thisValue, const ArgList& args)
377 JSValue* v = thisValue->getJSNumber();
379 return throwError(exec, TypeError);
381 double doublePrecision = args[0]->toIntegerPreserveNaN(exec);
382 double x = v->uncheckedGetNumber();
383 if (args[0]->isUndefined() || isnan(x) || isinf(x))
384 return jsString(exec, v->toString(exec));
392 if (!(doublePrecision >= 1 && doublePrecision <= 21)) // true for NaN
393 return throwError(exec, RangeError, "toPrecision() argument must be between 1 and 21");
394 int precision = (int)doublePrecision;
399 e = static_cast<int>(log10(x));
400 double tens = intPow10(e - precision + 1);
401 double n = floor(x / tens);
402 if (n < intPow10(precision - 1)) {
404 tens = intPow10(e - precision + 1);
408 if (fabs((n + 1.0) * tens - x) <= fabs(n * tens - x))
410 // maintain n < 10^(precision)
411 if (n >= intPow10(precision)) {
415 ASSERT(intPow10(precision - 1) <= n);
416 ASSERT(n < intPow10(precision));
418 m = integer_part_noexp(n);
419 if (e < -6 || e >= precision) {
421 m = m.substr(0, 1) + "." + m.substr(1);
423 return jsString(exec, s + m + "e+" + UString::from(e));
424 return jsString(exec, s + m + "e-" + UString::from(-e));
427 m = char_sequence('0', precision);
431 if (e == precision - 1)
432 return jsString(exec, s + m);
434 if (e + 1 < m.size())
435 return jsString(exec, s + m.substr(0, e + 1) + "." + m.substr(e + 1));
436 return jsString(exec, s + m);
438 return jsString(exec, s + "0." + char_sequence('0', -(e + 1)) + m);