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 "JSStringBuilder.h" 29 #include "Operations.h" 30 #include "PrototypeFunction.h" 31 #include "StringBuilder.h" 32 #include "dtoa.h" 33 #include <wtf/Assertions.h> 34 #include <wtf/MathExtras.h> 35 #include <wtf/Vector.h> 36 37 namespace JSC { 38 39 ASSERT_CLASS_FITS_IN_CELL(NumberPrototype); 40 41 static JSValue JSC_HOST_CALL numberProtoFuncToString(ExecState*, JSObject*, JSValue, const ArgList&); 42 static JSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState*, JSObject*, JSValue, const ArgList&); 43 static JSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState*, JSObject*, JSValue, const ArgList&); 44 static JSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState*, JSObject*, JSValue, const ArgList&); 45 static JSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState*, JSObject*, JSValue, const ArgList&); 46 static JSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState*, JSObject*, JSValue, const ArgList&); 47 48 // ECMA 15.7.4 49 50 NumberPrototype::NumberPrototype(ExecState* exec, NonNullPassRefPtr<Structure> structure, Structure* prototypeFunctionStructure) 51 : NumberObject(structure) 52 { 53 setInternalValue(jsNumber(exec, 0)); 54 55 // The constructor will be added later, after NumberConstructor has been constructed 56 57 putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, prototypeFunctionStructure, 1, exec->propertyNames().toString, numberProtoFuncToString), DontEnum); 58 putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, prototypeFunctionStructure, 0, exec->propertyNames().toLocaleString, numberProtoFuncToLocaleString), DontEnum); 59 putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, prototypeFunctionStructure, 0, exec->propertyNames().valueOf, numberProtoFuncValueOf), DontEnum); 60 putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, prototypeFunctionStructure, 1, exec->propertyNames().toFixed, numberProtoFuncToFixed), DontEnum); 61 putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, prototypeFunctionStructure, 1, exec->propertyNames().toExponential, numberProtoFuncToExponential), DontEnum); 62 putDirectFunctionWithoutTransition(exec, new (exec) NativeFunctionWrapper(exec, prototypeFunctionStructure, 1, exec->propertyNames().toPrecision, numberProtoFuncToPrecision), DontEnum); 63 } 64 65 // ------------------------------ Functions --------------------------- 66 67 // ECMA 15.7.4.2 - 15.7.4.7 68 69 static UString integerPartNoExp(double d) 70 { 71 int decimalPoint; 72 int sign; 73 char result[80]; 74 WTF::dtoa(result, d, 0, &decimalPoint, &sign, NULL); 75 bool resultIsInfOrNan = (decimalPoint == 9999); 76 size_t length = strlen(result); 77 78 StringBuilder builder; 79 builder.append(sign ? "-" : ""); 80 if (resultIsInfOrNan) 81 builder.append((const char*)result); 82 else if (decimalPoint <= 0) 83 builder.append("0"); 84 else { 85 Vector<char, 1024> buf(decimalPoint + 1); 86 87 if (static_cast<int>(length) <= decimalPoint) { 88 ASSERT(decimalPoint < 1024); 89 memcpy(buf.data(), result, length); 90 memset(buf.data() + length, '0', decimalPoint - length); 91 } else 92 strncpy(buf.data(), result, decimalPoint); 93 buf[decimalPoint] = '\0'; 94 95 builder.append((const char*)(buf.data())); 96 } 97 98 return builder.build(); 99 } 100 101 static UString charSequence(char c, int count) 102 { 103 Vector<char, 2048> buf(count + 1, c); 104 buf[count] = '\0'; 105 106 return UString(buf.data()); 107 } 108 109 static double intPow10(int e) 110 { 111 // This function uses the "exponentiation by squaring" algorithm and 112 // long double to quickly and precisely calculate integer powers of 10.0. 113 114 // This is a handy workaround for <rdar://problem/4494756> 115 116 if (e == 0) 117 return 1.0; 118 119 bool negative = e < 0; 120 unsigned exp = negative ? -e : e; 121 122 long double result = 10.0; 123 bool foundOne = false; 124 for (int bit = 31; bit >= 0; bit--) { 125 if (!foundOne) { 126 if ((exp >> bit) & 1) 127 foundOne = true; 128 } else { 129 result = result * result; 130 if ((exp >> bit) & 1) 131 result = result * 10.0; 132 } 133 } 134 135 if (negative) 136 return static_cast<double>(1.0 / result); 137 return static_cast<double>(result); 138 } 139 140 JSValue JSC_HOST_CALL numberProtoFuncToString(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 141 { 142 JSValue v = thisValue.getJSNumber(); 143 if (!v) 144 return throwError(exec, TypeError); 145 146 double radixAsDouble = args.at(0).toInteger(exec); // nan -> 0 147 if (radixAsDouble == 10 || args.at(0).isUndefined()) 148 return jsString(exec, v.toString(exec)); 149 150 if (radixAsDouble < 2 || radixAsDouble > 36) 151 return throwError(exec, RangeError, "toString() radix argument must be between 2 and 36"); 152 153 int radix = static_cast<int>(radixAsDouble); 154 const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 155 // INT_MAX results in 1024 characters left of the dot with radix 2 156 // give the same space on the right side. safety checks are in place 157 // unless someone finds a precise rule. 158 char s[2048 + 3]; 159 const char* lastCharInString = s + sizeof(s) - 1; 160 double x = v.uncheckedGetNumber(); 161 if (isnan(x) || isinf(x)) 162 return jsString(exec, UString::from(x)); 163 164 bool isNegative = x < 0.0; 165 if (isNegative) 166 x = -x; 167 168 double integerPart = floor(x); 169 char* decimalPoint = s + sizeof(s) / 2; 170 171 // convert integer portion 172 char* p = decimalPoint; 173 double d = integerPart; 174 do { 175 int remainderDigit = static_cast<int>(fmod(d, radix)); 176 *--p = digits[remainderDigit]; 177 d /= radix; 178 } while ((d <= -1.0 || d >= 1.0) && s < p); 179 180 if (isNegative) 181 *--p = '-'; 182 char* startOfResultString = p; 183 ASSERT(s <= startOfResultString); 184 185 d = x - integerPart; 186 p = decimalPoint; 187 const double epsilon = 0.001; // TODO: guessed. base on radix ? 188 bool hasFractionalPart = (d < -epsilon || d > epsilon); 189 if (hasFractionalPart) { 190 *p++ = '.'; 191 do { 192 d *= radix; 193 const int digit = static_cast<int>(d); 194 *p++ = digits[digit]; 195 d -= digit; 196 } while ((d < -epsilon || d > epsilon) && p < lastCharInString); 197 } 198 *p = '\0'; 199 ASSERT(p < s + sizeof(s)); 200 201 return jsString(exec, startOfResultString); 202 } 203 204 JSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 205 { 206 // FIXME: Not implemented yet. 207 208 JSValue v = thisValue.getJSNumber(); 209 if (!v) 210 return throwError(exec, TypeError); 211 212 return jsString(exec, v.toString(exec)); 213 } 214 215 JSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState* exec, JSObject*, JSValue thisValue, const ArgList&) 216 { 217 JSValue v = thisValue.getJSNumber(); 218 if (!v) 219 return throwError(exec, TypeError); 220 221 return v; 222 } 223 224 JSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 225 { 226 JSValue v = thisValue.getJSNumber(); 227 if (!v) 228 return throwError(exec, TypeError); 229 230 JSValue fractionDigits = args.at(0); 231 double df = fractionDigits.toInteger(exec); 232 if (!(df >= 0 && df <= 20)) 233 return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20"); 234 int f = static_cast<int>(df); 235 236 double x = v.uncheckedGetNumber(); 237 if (isnan(x)) 238 return jsNontrivialString(exec, "NaN"); 239 240 UString s; 241 if (x < 0) { 242 s = "-"; 243 x = -x; 244 } else { 245 s = ""; 246 if (x == -0.0) 247 x = 0; 248 } 249 250 if (x >= pow(10.0, 21.0)) 251 return jsString(exec, makeString(s, UString::from(x))); 252 253 const double tenToTheF = pow(10.0, f); 254 double n = floor(x * tenToTheF); 255 if (fabs(n / tenToTheF - x) >= fabs((n + 1) / tenToTheF - x)) 256 n++; 257 258 UString m = integerPartNoExp(n); 259 260 int k = m.size(); 261 if (k <= f) { 262 StringBuilder z; 263 for (int i = 0; i < f + 1 - k; i++) 264 z.append('0'); 265 z.append(m); 266 m = z.build(); 267 k = f + 1; 268 ASSERT(k == m.size()); 269 } 270 int kMinusf = k - f; 271 272 if (kMinusf < m.size()) 273 return jsString(exec, makeString(s, m.substr(0, kMinusf), ".", m.substr(kMinusf))); 274 return jsString(exec, makeString(s, m.substr(0, kMinusf))); 275 } 276 277 static void fractionalPartToString(char* buf, int& i, const char* result, int resultLength, int fractionalDigits) 278 { 279 if (fractionalDigits <= 0) 280 return; 281 282 int fDigitsInResult = static_cast<int>(resultLength) - 1; 283 buf[i++] = '.'; 284 if (fDigitsInResult > 0) { 285 if (fractionalDigits < fDigitsInResult) { 286 strncpy(buf + i, result + 1, fractionalDigits); 287 i += fractionalDigits; 288 } else { 289 ASSERT(i + resultLength - 1 < 80); 290 memcpy(buf + i, result + 1, resultLength - 1); 291 i += static_cast<int>(resultLength) - 1; 292 } 293 } 294 295 for (int j = 0; j < fractionalDigits - fDigitsInResult; j++) 296 buf[i++] = '0'; 297 } 298 299 static void exponentialPartToString(char* buf, int& i, int decimalPoint) 300 { 301 buf[i++] = 'e'; 302 // decimalPoint can't be more than 3 digits decimal given the 303 // nature of float representation 304 int exponential = decimalPoint - 1; 305 buf[i++] = (exponential >= 0) ? '+' : '-'; 306 if (exponential < 0) 307 exponential *= -1; 308 if (exponential >= 100) 309 buf[i++] = static_cast<char>('0' + exponential / 100); 310 if (exponential >= 10) 311 buf[i++] = static_cast<char>('0' + (exponential % 100) / 10); 312 buf[i++] = static_cast<char>('0' + exponential % 10); 313 } 314 315 JSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 316 { 317 JSValue v = thisValue.getJSNumber(); 318 if (!v) 319 return throwError(exec, TypeError); 320 321 double x = v.uncheckedGetNumber(); 322 323 if (isnan(x) || isinf(x)) 324 return jsString(exec, UString::from(x)); 325 326 JSValue fractionalDigitsValue = args.at(0); 327 double df = fractionalDigitsValue.toInteger(exec); 328 if (!(df >= 0 && df <= 20)) 329 return throwError(exec, RangeError, "toExponential() argument must between 0 and 20"); 330 int fractionalDigits = static_cast<int>(df); 331 bool includeAllDigits = fractionalDigitsValue.isUndefined(); 332 333 int decimalAdjust = 0; 334 if (x && !includeAllDigits) { 335 double logx = floor(log10(fabs(x))); 336 x /= pow(10.0, logx); 337 const double tenToTheF = pow(10.0, fractionalDigits); 338 double fx = floor(x * tenToTheF) / tenToTheF; 339 double cx = ceil(x * tenToTheF) / tenToTheF; 340 341 if (fabs(fx - x) < fabs(cx - x)) 342 x = fx; 343 else 344 x = cx; 345 346 decimalAdjust = static_cast<int>(logx); 347 } 348 349 if (isnan(x)) 350 return jsNontrivialString(exec, "NaN"); 351 352 if (x == -0.0) // (-0.0).toExponential() should print as 0 instead of -0 353 x = 0; 354 355 int decimalPoint; 356 int sign; 357 char result[80]; 358 WTF::dtoa(result, x, 0, &decimalPoint, &sign, NULL); 359 size_t resultLength = strlen(result); 360 decimalPoint += decimalAdjust; 361 362 int i = 0; 363 char buf[80]; // digit + '.' + fractionDigits (max 20) + 'e' + sign + exponent (max?) 364 if (sign) 365 buf[i++] = '-'; 366 367 // ? 9999 is the magical "result is Inf or NaN" value. what's 999?? 368 if (decimalPoint == 999) { 369 ASSERT(i + resultLength < 80); 370 memcpy(buf + i, result, resultLength); 371 buf[i + resultLength] = '\0'; 372 } else { 373 buf[i++] = result[0]; 374 375 if (includeAllDigits) 376 fractionalDigits = static_cast<int>(resultLength) - 1; 377 378 fractionalPartToString(buf, i, result, resultLength, fractionalDigits); 379 exponentialPartToString(buf, i, decimalPoint); 380 buf[i++] = '\0'; 381 } 382 ASSERT(i <= 80); 383 384 return jsString(exec, buf); 385 } 386 387 JSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState* exec, JSObject*, JSValue thisValue, const ArgList& args) 388 { 389 JSValue v = thisValue.getJSNumber(); 390 if (!v) 391 return throwError(exec, TypeError); 392 393 double doublePrecision = args.at(0).toIntegerPreserveNaN(exec); 394 double x = v.uncheckedGetNumber(); 395 if (args.at(0).isUndefined() || isnan(x) || isinf(x)) 396 return jsString(exec, v.toString(exec)); 397 398 UString s; 399 if (x < 0) { 400 s = "-"; 401 x = -x; 402 } else 403 s = ""; 404 405 if (!(doublePrecision >= 1 && doublePrecision <= 21)) // true for NaN 406 return throwError(exec, RangeError, "toPrecision() argument must be between 1 and 21"); 407 int precision = static_cast<int>(doublePrecision); 408 409 int e = 0; 410 UString m; 411 if (x) { 412 e = static_cast<int>(log10(x)); 413 double tens = intPow10(e - precision + 1); 414 double n = floor(x / tens); 415 if (n < intPow10(precision - 1)) { 416 e = e - 1; 417 tens = intPow10(e - precision + 1); 418 n = floor(x / tens); 419 } 420 421 if (fabs((n + 1.0) * tens - x) <= fabs(n * tens - x)) 422 ++n; 423 // maintain n < 10^(precision) 424 if (n >= intPow10(precision)) { 425 n /= 10.0; 426 e += 1; 427 } 428 ASSERT(intPow10(precision - 1) <= n); 429 ASSERT(n < intPow10(precision)); 430 431 m = integerPartNoExp(n); 432 if (e < -6 || e >= precision) { 433 if (m.size() > 1) 434 m = makeString(m.substr(0, 1), ".", m.substr(1)); 435 if (e >= 0) 436 return jsMakeNontrivialString(exec, s, m, "e+", UString::from(e)); 437 return jsMakeNontrivialString(exec, s, m, "e-", UString::from(-e)); 438 } 439 } else { 440 m = charSequence('0', precision); 441 e = 0; 442 } 443 444 if (e == precision - 1) 445 return jsString(exec, makeString(s, m)); 446 if (e >= 0) { 447 if (e + 1 < m.size()) 448 return jsString(exec, makeString(s, m.substr(0, e + 1), ".", m.substr(e + 1))); 449 return jsString(exec, makeString(s, m)); 450 } 451 return jsMakeNontrivialString(exec, s, "0.", charSequence('0', -(e + 1)), m); 452 } 453 454 } // namespace JSC 455