1 /* 2 * Copyright (C) 1999-2000,2003 Harri Porten (porten (at) kde.org) 3 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 4 * 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. 9 * 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. 14 * 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 18 * USA 19 * 20 */ 21 22 #include "config.h" 23 #include "NumberPrototype.h" 24 25 #include "Error.h" 26 #include "JSFunction.h" 27 #include "JSString.h" 28 #include "Operations.h" 29 #include "dtoa.h" 30 #include <wtf/Assertions.h> 31 #include <wtf/DecimalNumber.h> 32 #include <wtf/MathExtras.h> 33 #include <wtf/Vector.h> 34 35 namespace JSC { 36 37 ASSERT_CLASS_FITS_IN_CELL(NumberPrototype); 38 39 static EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState*); 40 static EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState*); 41 static EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState*); 42 static EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState*); 43 static EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState*); 44 static EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState*); 45 46 // ECMA 15.7.4 47 48 NumberPrototype::NumberPrototype(ExecState* exec, JSGlobalObject* globalObject, Structure* structure, Structure* functionStructure) 49 : NumberObject(exec->globalData(), structure) 50 { 51 setInternalValue(exec->globalData(), jsNumber(0)); 52 53 // The constructor will be added later, after NumberConstructor has been constructed 54 55 putDirectFunctionWithoutTransition(exec, new (exec) JSFunction(exec, globalObject, functionStructure, 1, exec->propertyNames().toString, numberProtoFuncToString), DontEnum); 56 putDirectFunctionWithoutTransition(exec, new (exec) JSFunction(exec, globalObject, functionStructure, 0, exec->propertyNames().toLocaleString, numberProtoFuncToLocaleString), DontEnum); 57 putDirectFunctionWithoutTransition(exec, new (exec) JSFunction(exec, globalObject, functionStructure, 0, exec->propertyNames().valueOf, numberProtoFuncValueOf), DontEnum); 58 putDirectFunctionWithoutTransition(exec, new (exec) JSFunction(exec, globalObject, functionStructure, 1, exec->propertyNames().toFixed, numberProtoFuncToFixed), DontEnum); 59 putDirectFunctionWithoutTransition(exec, new (exec) JSFunction(exec, globalObject, functionStructure, 1, exec->propertyNames().toExponential, numberProtoFuncToExponential), DontEnum); 60 putDirectFunctionWithoutTransition(exec, new (exec) JSFunction(exec, globalObject, functionStructure, 1, exec->propertyNames().toPrecision, numberProtoFuncToPrecision), DontEnum); 61 } 62 63 // ------------------------------ Functions --------------------------- 64 65 // ECMA 15.7.4.2 - 15.7.4.7 66 67 static ALWAYS_INLINE bool toThisNumber(JSValue thisValue, double &x) 68 { 69 JSValue v = thisValue.getJSNumber(); 70 if (UNLIKELY(!v)) 71 return false; 72 x = v.uncheckedGetNumber(); 73 return true; 74 } 75 76 static ALWAYS_INLINE bool getIntegerArgumentInRange(ExecState* exec, int low, int high, int& result, bool& isUndefined) 77 { 78 result = 0; 79 isUndefined = false; 80 81 JSValue argument0 = exec->argument(0); 82 if (argument0.isUndefined()) { 83 isUndefined = true; 84 return true; 85 } 86 87 double asDouble = argument0.toInteger(exec); 88 if (asDouble < low || asDouble > high) 89 return false; 90 91 result = static_cast<int>(asDouble); 92 return true; 93 } 94 95 // toExponential converts a number to a string, always formatting as an expoential. 96 // This method takes an optional argument specifying a number of *decimal places* 97 // to round the significand to (or, put another way, this method optionally rounds 98 // to argument-plus-one significant figures). 99 EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState* exec) 100 { 101 // Get x (the double value of this, which should be a Number). 102 double x; 103 if (!toThisNumber(exec->hostThisValue(), x)) 104 return throwVMTypeError(exec); 105 106 // Get the argument. 107 int decimalPlacesInExponent; 108 bool isUndefined; 109 if (!getIntegerArgumentInRange(exec, 0, 20, decimalPlacesInExponent, isUndefined)) 110 return throwVMError(exec, createRangeError(exec, "toExponential() argument must be between 0 and 20")); 111 112 // Handle NaN and Infinity. 113 if (isnan(x) || isinf(x)) 114 return JSValue::encode(jsString(exec, UString::number(x))); 115 116 // Round if the argument is not undefined, always format as exponential. 117 NumberToStringBuffer buffer; 118 unsigned length = isUndefined 119 ? DecimalNumber(x).toStringExponential(buffer, WTF::NumberToStringBufferLength) 120 : DecimalNumber(x, RoundingSignificantFigures, decimalPlacesInExponent + 1).toStringExponential(buffer, WTF::NumberToStringBufferLength); 121 122 return JSValue::encode(jsString(exec, UString(buffer, length))); 123 } 124 125 // toFixed converts a number to a string, always formatting as an a decimal fraction. 126 // This method takes an argument specifying a number of decimal places to round the 127 // significand to. However when converting large values (1e+21 and above) this 128 // method will instead fallback to calling ToString. 129 EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState* exec) 130 { 131 // Get x (the double value of this, which should be a Number). 132 JSValue thisValue = exec->hostThisValue(); 133 JSValue v = thisValue.getJSNumber(); 134 if (!v) 135 return throwVMTypeError(exec); 136 double x = v.uncheckedGetNumber(); 137 138 // Get the argument. 139 int decimalPlaces; 140 bool isUndefined; // This is ignored; undefined treated as 0. 141 if (!getIntegerArgumentInRange(exec, 0, 20, decimalPlaces, isUndefined)) 142 return throwVMError(exec, createRangeError(exec, "toFixed() argument must be between 0 and 20")); 143 144 // 15.7.4.5.7 states "If x >= 10^21, then let m = ToString(x)" 145 // This also covers Ininity, and structure the check so that NaN 146 // values are also handled by numberToString 147 if (!(fabs(x) < 1e+21)) 148 return JSValue::encode(jsString(exec, UString::number(x))); 149 150 // The check above will return false for NaN or Infinity, these will be 151 // handled by numberToString. 152 ASSERT(!isnan(x) && !isinf(x)); 153 154 // Convert to decimal with rounding, and format as decimal. 155 NumberToStringBuffer buffer; 156 unsigned length = DecimalNumber(x, RoundingDecimalPlaces, decimalPlaces).toStringDecimal(buffer, WTF::NumberToStringBufferLength); 157 return JSValue::encode(jsString(exec, UString(buffer, length))); 158 } 159 160 // toPrecision converts a number to a string, takeing an argument specifying a 161 // number of significant figures to round the significand to. For positive 162 // exponent, all values that can be represented using a decimal fraction will 163 // be, e.g. when rounding to 3 s.f. any value up to 999 will be formated as a 164 // decimal, whilst 1000 is converted to the exponential representation 1.00e+3. 165 // For negative exponents values >= 1e-6 are formated as decimal fractions, 166 // with smaller values converted to exponential representation. 167 EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState* exec) 168 { 169 // Get x (the double value of this, which should be a Number). 170 JSValue thisValue = exec->hostThisValue(); 171 JSValue v = thisValue.getJSNumber(); 172 if (!v) 173 return throwVMTypeError(exec); 174 double x = v.uncheckedGetNumber(); 175 176 // Get the argument. 177 int significantFigures; 178 bool isUndefined; 179 if (!getIntegerArgumentInRange(exec, 1, 21, significantFigures, isUndefined)) 180 return throwVMError(exec, createRangeError(exec, "toPrecision() argument must be between 1 and 21")); 181 182 // To precision called with no argument is treated as ToString. 183 if (isUndefined) 184 return JSValue::encode(jsString(exec, UString::number(x))); 185 186 // Handle NaN and Infinity. 187 if (isnan(x) || isinf(x)) 188 return JSValue::encode(jsString(exec, UString::number(x))); 189 190 // Convert to decimal with rounding. 191 DecimalNumber number(x, RoundingSignificantFigures, significantFigures); 192 // If number is in the range 1e-6 <= x < pow(10, significantFigures) then format 193 // as decimal. Otherwise, format the number as an exponential. Decimal format 194 // demands a minimum of (exponent + 1) digits to represent a number, for example 195 // 1234 (1.234e+3) requires 4 digits. (See ECMA-262 15.7.4.7.10.c) 196 NumberToStringBuffer buffer; 197 unsigned length = number.exponent() >= -6 && number.exponent() < significantFigures 198 ? number.toStringDecimal(buffer, WTF::NumberToStringBufferLength) 199 : number.toStringExponential(buffer, WTF::NumberToStringBufferLength); 200 return JSValue::encode(jsString(exec, UString(buffer, length))); 201 } 202 203 EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState* exec) 204 { 205 JSValue thisValue = exec->hostThisValue(); 206 JSValue v = thisValue.getJSNumber(); 207 if (!v) 208 return throwVMTypeError(exec); 209 210 JSValue radixValue = exec->argument(0); 211 int radix; 212 if (radixValue.isInt32()) 213 radix = radixValue.asInt32(); 214 else if (radixValue.isUndefined()) 215 radix = 10; 216 else 217 radix = static_cast<int>(radixValue.toInteger(exec)); // nan -> 0 218 219 if (radix == 10) 220 return JSValue::encode(jsString(exec, v.toString(exec))); 221 222 static const char* const digits = "0123456789abcdefghijklmnopqrstuvwxyz"; 223 224 // Fast path for number to character conversion. 225 if (radix == 36) { 226 if (v.isInt32()) { 227 int x = v.asInt32(); 228 if (static_cast<unsigned>(x) < 36) { // Exclude negatives 229 JSGlobalData* globalData = &exec->globalData(); 230 return JSValue::encode(globalData->smallStrings.singleCharacterString(globalData, digits[x])); 231 } 232 } 233 } 234 235 if (radix < 2 || radix > 36) 236 return throwVMError(exec, createRangeError(exec, "toString() radix argument must be between 2 and 36")); 237 238 // INT_MAX results in 1024 characters left of the dot with radix 2 239 // give the same space on the right side. safety checks are in place 240 // unless someone finds a precise rule. 241 char s[2048 + 3]; 242 const char* lastCharInString = s + sizeof(s) - 1; 243 double x = v.uncheckedGetNumber(); 244 if (isnan(x) || isinf(x)) 245 return JSValue::encode(jsString(exec, UString::number(x))); 246 247 bool isNegative = x < 0.0; 248 if (isNegative) 249 x = -x; 250 251 double integerPart = floor(x); 252 char* decimalPoint = s + sizeof(s) / 2; 253 254 // convert integer portion 255 char* p = decimalPoint; 256 double d = integerPart; 257 do { 258 int remainderDigit = static_cast<int>(fmod(d, radix)); 259 *--p = digits[remainderDigit]; 260 d /= radix; 261 } while ((d <= -1.0 || d >= 1.0) && s < p); 262 263 if (isNegative) 264 *--p = '-'; 265 char* startOfResultString = p; 266 ASSERT(s <= startOfResultString); 267 268 d = x - integerPart; 269 p = decimalPoint; 270 const double epsilon = 0.001; // TODO: guessed. base on radix ? 271 bool hasFractionalPart = (d < -epsilon || d > epsilon); 272 if (hasFractionalPart) { 273 *p++ = '.'; 274 do { 275 d *= radix; 276 const int digit = static_cast<int>(d); 277 *p++ = digits[digit]; 278 d -= digit; 279 } while ((d < -epsilon || d > epsilon) && p < lastCharInString); 280 } 281 *p = '\0'; 282 ASSERT(p < s + sizeof(s)); 283 284 return JSValue::encode(jsString(exec, startOfResultString)); 285 } 286 287 EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState* exec) 288 { 289 JSValue thisValue = exec->hostThisValue(); 290 // FIXME: Not implemented yet. 291 292 JSValue v = thisValue.getJSNumber(); 293 if (!v) 294 return throwVMTypeError(exec); 295 296 return JSValue::encode(jsString(exec, v.toString(exec))); 297 } 298 299 EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState* exec) 300 { 301 JSValue thisValue = exec->hostThisValue(); 302 JSValue v = thisValue.getJSNumber(); 303 if (!v) 304 return throwVMTypeError(exec); 305 306 return JSValue::encode(v); 307 } 308 309 } // namespace JSC 310