/* * Copyright (C) 1999-2000,2003 Harri Porten (porten@kde.org) * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA * */ #include "config.h" #include "NumberPrototype.h" #include "Error.h" #include "JSFunction.h" #include "JSString.h" #include "Operations.h" #include "PrototypeFunction.h" #include "dtoa.h" #include #include #include #include namespace JSC { ASSERT_CLASS_FITS_IN_CELL(NumberPrototype); static EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState*); static EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState*); static EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState*); static EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState*); static EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState*); static EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState*); // ECMA 15.7.4 NumberPrototype::NumberPrototype(ExecState* exec, JSGlobalObject* globalObject, NonNullPassRefPtr structure, Structure* prototypeFunctionStructure) : NumberObject(structure) { setInternalValue(jsNumber(exec, 0)); // The constructor will be added later, after NumberConstructor has been constructed putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, globalObject, prototypeFunctionStructure, 1, exec->propertyNames().toString, numberProtoFuncToString), DontEnum); putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, globalObject, prototypeFunctionStructure, 0, exec->propertyNames().toLocaleString, numberProtoFuncToLocaleString), DontEnum); putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, globalObject, prototypeFunctionStructure, 0, exec->propertyNames().valueOf, numberProtoFuncValueOf), DontEnum); putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, globalObject, prototypeFunctionStructure, 1, exec->propertyNames().toFixed, numberProtoFuncToFixed), DontEnum); putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, globalObject, prototypeFunctionStructure, 1, exec->propertyNames().toExponential, numberProtoFuncToExponential), DontEnum); putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, globalObject, prototypeFunctionStructure, 1, exec->propertyNames().toPrecision, numberProtoFuncToPrecision), DontEnum); } // ------------------------------ Functions --------------------------- // ECMA 15.7.4.2 - 15.7.4.7 static ALWAYS_INLINE bool toThisNumber(JSValue thisValue, double &x) { JSValue v = thisValue.getJSNumber(); if (UNLIKELY(!v)) return false; x = v.uncheckedGetNumber(); return true; } static ALWAYS_INLINE bool getIntegerArgumentInRange(ExecState* exec, int low, int high, int& result, bool& isUndefined) { result = 0; isUndefined = false; JSValue argument0 = exec->argument(0); if (argument0.isUndefined()) { isUndefined = true; return true; } double asDouble = argument0.toInteger(exec); if (asDouble < low || asDouble > high) return false; result = static_cast(asDouble); return true; } // toExponential converts a number to a string, always formatting as an expoential. // This method takes an optional argument specifying a number of *decimal places* // to round the significand to (or, put another way, this method optionally rounds // to argument-plus-one significant figures). EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState* exec) { // Get x (the double value of this, which should be a Number). double x; if (!toThisNumber(exec->hostThisValue(), x)) return throwVMTypeError(exec); // Get the argument. int decimalPlacesInExponent; bool isUndefined; if (!getIntegerArgumentInRange(exec, 0, 20, decimalPlacesInExponent, isUndefined)) return throwVMError(exec, createRangeError(exec, "toExponential() argument must be between 0 and 20")); // Handle NaN and Infinity. if (isnan(x) || isinf(x)) return JSValue::encode(jsString(exec, UString::number(x))); // Round if the argument is not undefined, always format as exponential. NumberToStringBuffer buffer; unsigned length = isUndefined ? DecimalNumber(x).toStringExponential(buffer, WTF::NumberToStringBufferLength) : DecimalNumber(x, RoundingSignificantFigures, decimalPlacesInExponent + 1).toStringExponential(buffer, WTF::NumberToStringBufferLength); return JSValue::encode(jsString(exec, UString(buffer, length))); } // toFixed converts a number to a string, always formatting as an a decimal fraction. // This method takes an argument specifying a number of decimal places to round the // significand to. However when converting large values (1e+21 and above) this // method will instead fallback to calling ToString. EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState* exec) { // Get x (the double value of this, which should be a Number). JSValue thisValue = exec->hostThisValue(); JSValue v = thisValue.getJSNumber(); if (!v) return throwVMTypeError(exec); double x = v.uncheckedGetNumber(); // Get the argument. int decimalPlaces; bool isUndefined; // This is ignored; undefined treated as 0. if (!getIntegerArgumentInRange(exec, 0, 20, decimalPlaces, isUndefined)) return throwVMError(exec, createRangeError(exec, "toFixed() argument must be between 0 and 20")); // 15.7.4.5.7 states "If x >= 10^21, then let m = ToString(x)" // This also covers Ininity, and structure the check so that NaN // values are also handled by numberToString if (!(fabs(x) < 1e+21)) return JSValue::encode(jsString(exec, UString::number(x))); // The check above will return false for NaN or Infinity, these will be // handled by numberToString. ASSERT(!isnan(x) && !isinf(x)); // Convert to decimal with rounding, and format as decimal. NumberToStringBuffer buffer; unsigned length = DecimalNumber(x, RoundingDecimalPlaces, decimalPlaces).toStringDecimal(buffer, WTF::NumberToStringBufferLength); return JSValue::encode(jsString(exec, UString(buffer, length))); } // toPrecision converts a number to a string, takeing an argument specifying a // number of significant figures to round the significand to. For positive // exponent, all values that can be represented using a decimal fraction will // be, e.g. when rounding to 3 s.f. any value up to 999 will be formated as a // decimal, whilst 1000 is converted to the exponential representation 1.00e+3. // For negative exponents values >= 1e-6 are formated as decimal fractions, // with smaller values converted to exponential representation. EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState* exec) { // Get x (the double value of this, which should be a Number). JSValue thisValue = exec->hostThisValue(); JSValue v = thisValue.getJSNumber(); if (!v) return throwVMTypeError(exec); double x = v.uncheckedGetNumber(); // Get the argument. int significantFigures; bool isUndefined; if (!getIntegerArgumentInRange(exec, 1, 21, significantFigures, isUndefined)) return throwVMError(exec, createRangeError(exec, "toPrecision() argument must be between 1 and 21")); // To precision called with no argument is treated as ToString. if (isUndefined) return JSValue::encode(jsString(exec, UString::number(x))); // Handle NaN and Infinity. if (isnan(x) || isinf(x)) return JSValue::encode(jsString(exec, UString::number(x))); // Convert to decimal with rounding. DecimalNumber number(x, RoundingSignificantFigures, significantFigures); // If number is in the range 1e-6 <= x < pow(10, significantFigures) then format // as decimal. Otherwise, format the number as an exponential. Decimal format // demands a minimum of (exponent + 1) digits to represent a number, for example // 1234 (1.234e+3) requires 4 digits. (See ECMA-262 15.7.4.7.10.c) NumberToStringBuffer buffer; unsigned length = number.exponent() >= -6 && number.exponent() < significantFigures ? number.toStringDecimal(buffer, WTF::NumberToStringBufferLength) : number.toStringExponential(buffer, WTF::NumberToStringBufferLength); return JSValue::encode(jsString(exec, UString(buffer, length))); } EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState* exec) { JSValue thisValue = exec->hostThisValue(); JSValue v = thisValue.getJSNumber(); if (!v) return throwVMTypeError(exec); JSValue radixValue = exec->argument(0); int radix; if (radixValue.isInt32()) radix = radixValue.asInt32(); else if (radixValue.isUndefined()) radix = 10; else radix = static_cast(radixValue.toInteger(exec)); // nan -> 0 if (radix == 10) return JSValue::encode(jsString(exec, v.toString(exec))); static const char* const digits = "0123456789abcdefghijklmnopqrstuvwxyz"; // Fast path for number to character conversion. if (radix == 36) { if (v.isInt32()) { int x = v.asInt32(); if (static_cast(x) < 36) { // Exclude negatives JSGlobalData* globalData = &exec->globalData(); return JSValue::encode(globalData->smallStrings.singleCharacterString(globalData, digits[x])); } } } if (radix < 2 || radix > 36) return throwVMError(exec, createRangeError(exec, "toString() radix argument must be between 2 and 36")); // INT_MAX results in 1024 characters left of the dot with radix 2 // give the same space on the right side. safety checks are in place // unless someone finds a precise rule. char s[2048 + 3]; const char* lastCharInString = s + sizeof(s) - 1; double x = v.uncheckedGetNumber(); if (isnan(x) || isinf(x)) return JSValue::encode(jsString(exec, UString::number(x))); bool isNegative = x < 0.0; if (isNegative) x = -x; double integerPart = floor(x); char* decimalPoint = s + sizeof(s) / 2; // convert integer portion char* p = decimalPoint; double d = integerPart; do { int remainderDigit = static_cast(fmod(d, radix)); *--p = digits[remainderDigit]; d /= radix; } while ((d <= -1.0 || d >= 1.0) && s < p); if (isNegative) *--p = '-'; char* startOfResultString = p; ASSERT(s <= startOfResultString); d = x - integerPart; p = decimalPoint; const double epsilon = 0.001; // TODO: guessed. base on radix ? bool hasFractionalPart = (d < -epsilon || d > epsilon); if (hasFractionalPart) { *p++ = '.'; do { d *= radix; const int digit = static_cast(d); *p++ = digits[digit]; d -= digit; } while ((d < -epsilon || d > epsilon) && p < lastCharInString); } *p = '\0'; ASSERT(p < s + sizeof(s)); return JSValue::encode(jsString(exec, startOfResultString)); } EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState* exec) { JSValue thisValue = exec->hostThisValue(); // FIXME: Not implemented yet. JSValue v = thisValue.getJSNumber(); if (!v) return throwVMTypeError(exec); return JSValue::encode(jsString(exec, v.toString(exec))); } EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState* exec) { JSValue thisValue = exec->hostThisValue(); JSValue v = thisValue.getJSNumber(); if (!v) return throwVMTypeError(exec); return JSValue::encode(v); } } // namespace JSC