Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * vim: set ts=8 sts=4 et sw=4 tw=99: |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | /* |
michael@0 | 8 | * JS number type and wrapper class. |
michael@0 | 9 | */ |
michael@0 | 10 | |
michael@0 | 11 | #include "jsnum.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "mozilla/FloatingPoint.h" |
michael@0 | 14 | #include "mozilla/PodOperations.h" |
michael@0 | 15 | #include "mozilla/RangedPtr.h" |
michael@0 | 16 | |
michael@0 | 17 | #ifdef HAVE_LOCALECONV |
michael@0 | 18 | #include <locale.h> |
michael@0 | 19 | #endif |
michael@0 | 20 | #include <math.h> |
michael@0 | 21 | #include <string.h> |
michael@0 | 22 | |
michael@0 | 23 | #include "double-conversion.h" |
michael@0 | 24 | #include "jsatom.h" |
michael@0 | 25 | #include "jscntxt.h" |
michael@0 | 26 | #include "jsdtoa.h" |
michael@0 | 27 | #include "jsobj.h" |
michael@0 | 28 | #include "jsstr.h" |
michael@0 | 29 | #include "jstypes.h" |
michael@0 | 30 | |
michael@0 | 31 | #include "vm/GlobalObject.h" |
michael@0 | 32 | #include "vm/NumericConversions.h" |
michael@0 | 33 | #include "vm/StringBuffer.h" |
michael@0 | 34 | |
michael@0 | 35 | #include "jsatominlines.h" |
michael@0 | 36 | |
michael@0 | 37 | #include "vm/NumberObject-inl.h" |
michael@0 | 38 | #include "vm/String-inl.h" |
michael@0 | 39 | |
michael@0 | 40 | using namespace js; |
michael@0 | 41 | using namespace js::types; |
michael@0 | 42 | |
michael@0 | 43 | using mozilla::Abs; |
michael@0 | 44 | using mozilla::MinNumberValue; |
michael@0 | 45 | using mozilla::NegativeInfinity; |
michael@0 | 46 | using mozilla::PodCopy; |
michael@0 | 47 | using mozilla::PositiveInfinity; |
michael@0 | 48 | using mozilla::RangedPtr; |
michael@0 | 49 | using JS::GenericNaN; |
michael@0 | 50 | |
michael@0 | 51 | /* |
michael@0 | 52 | * If we're accumulating a decimal number and the number is >= 2^53, then the |
michael@0 | 53 | * fast result from the loop in Get{Prefix,Decimal}Integer may be inaccurate. |
michael@0 | 54 | * Call js_strtod_harder to get the correct answer. |
michael@0 | 55 | */ |
michael@0 | 56 | static bool |
michael@0 | 57 | ComputeAccurateDecimalInteger(ThreadSafeContext *cx, |
michael@0 | 58 | const jschar *start, const jschar *end, double *dp) |
michael@0 | 59 | { |
michael@0 | 60 | size_t length = end - start; |
michael@0 | 61 | char *cstr = cx->pod_malloc<char>(length + 1); |
michael@0 | 62 | if (!cstr) |
michael@0 | 63 | return false; |
michael@0 | 64 | |
michael@0 | 65 | for (size_t i = 0; i < length; i++) { |
michael@0 | 66 | char c = char(start[i]); |
michael@0 | 67 | JS_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')); |
michael@0 | 68 | cstr[i] = c; |
michael@0 | 69 | } |
michael@0 | 70 | cstr[length] = 0; |
michael@0 | 71 | |
michael@0 | 72 | char *estr; |
michael@0 | 73 | int err = 0; |
michael@0 | 74 | *dp = js_strtod_harder(cx->dtoaState(), cstr, &estr, &err); |
michael@0 | 75 | if (err == JS_DTOA_ENOMEM) { |
michael@0 | 76 | js_ReportOutOfMemory(cx); |
michael@0 | 77 | js_free(cstr); |
michael@0 | 78 | return false; |
michael@0 | 79 | } |
michael@0 | 80 | js_free(cstr); |
michael@0 | 81 | return true; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | namespace { |
michael@0 | 85 | |
michael@0 | 86 | class BinaryDigitReader |
michael@0 | 87 | { |
michael@0 | 88 | const int base; /* Base of number; must be a power of 2 */ |
michael@0 | 89 | int digit; /* Current digit value in radix given by base */ |
michael@0 | 90 | int digitMask; /* Mask to extract the next bit from digit */ |
michael@0 | 91 | const jschar *start; /* Pointer to the remaining digits */ |
michael@0 | 92 | const jschar *end; /* Pointer to first non-digit */ |
michael@0 | 93 | |
michael@0 | 94 | public: |
michael@0 | 95 | BinaryDigitReader(int base, const jschar *start, const jschar *end) |
michael@0 | 96 | : base(base), digit(0), digitMask(0), start(start), end(end) |
michael@0 | 97 | { |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | /* Return the next binary digit from the number, or -1 if done. */ |
michael@0 | 101 | int nextDigit() { |
michael@0 | 102 | if (digitMask == 0) { |
michael@0 | 103 | if (start == end) |
michael@0 | 104 | return -1; |
michael@0 | 105 | |
michael@0 | 106 | int c = *start++; |
michael@0 | 107 | JS_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')); |
michael@0 | 108 | if ('0' <= c && c <= '9') |
michael@0 | 109 | digit = c - '0'; |
michael@0 | 110 | else if ('a' <= c && c <= 'z') |
michael@0 | 111 | digit = c - 'a' + 10; |
michael@0 | 112 | else |
michael@0 | 113 | digit = c - 'A' + 10; |
michael@0 | 114 | digitMask = base >> 1; |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | int bit = (digit & digitMask) != 0; |
michael@0 | 118 | digitMask >>= 1; |
michael@0 | 119 | return bit; |
michael@0 | 120 | } |
michael@0 | 121 | }; |
michael@0 | 122 | |
michael@0 | 123 | } /* anonymous namespace */ |
michael@0 | 124 | |
michael@0 | 125 | /* |
michael@0 | 126 | * The fast result might also have been inaccurate for power-of-two bases. This |
michael@0 | 127 | * happens if the addition in value * 2 + digit causes a round-down to an even |
michael@0 | 128 | * least significant mantissa bit when the first dropped bit is a one. If any |
michael@0 | 129 | * of the following digits in the number (which haven't been added in yet) are |
michael@0 | 130 | * nonzero, then the correct action would have been to round up instead of |
michael@0 | 131 | * down. An example occurs when reading the number 0x1000000000000081, which |
michael@0 | 132 | * rounds to 0x1000000000000000 instead of 0x1000000000000100. |
michael@0 | 133 | */ |
michael@0 | 134 | static double |
michael@0 | 135 | ComputeAccurateBinaryBaseInteger(const jschar *start, const jschar *end, int base) |
michael@0 | 136 | { |
michael@0 | 137 | BinaryDigitReader bdr(base, start, end); |
michael@0 | 138 | |
michael@0 | 139 | /* Skip leading zeroes. */ |
michael@0 | 140 | int bit; |
michael@0 | 141 | do { |
michael@0 | 142 | bit = bdr.nextDigit(); |
michael@0 | 143 | } while (bit == 0); |
michael@0 | 144 | |
michael@0 | 145 | JS_ASSERT(bit == 1); // guaranteed by Get{Prefix,Decimal}Integer |
michael@0 | 146 | |
michael@0 | 147 | /* Gather the 53 significant bits (including the leading 1). */ |
michael@0 | 148 | double value = 1.0; |
michael@0 | 149 | for (int j = 52; j > 0; j--) { |
michael@0 | 150 | bit = bdr.nextDigit(); |
michael@0 | 151 | if (bit < 0) |
michael@0 | 152 | return value; |
michael@0 | 153 | value = value * 2 + bit; |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | /* bit2 is the 54th bit (the first dropped from the mantissa). */ |
michael@0 | 157 | int bit2 = bdr.nextDigit(); |
michael@0 | 158 | if (bit2 >= 0) { |
michael@0 | 159 | double factor = 2.0; |
michael@0 | 160 | int sticky = 0; /* sticky is 1 if any bit beyond the 54th is 1 */ |
michael@0 | 161 | int bit3; |
michael@0 | 162 | |
michael@0 | 163 | while ((bit3 = bdr.nextDigit()) >= 0) { |
michael@0 | 164 | sticky |= bit3; |
michael@0 | 165 | factor *= 2; |
michael@0 | 166 | } |
michael@0 | 167 | value += bit2 & (bit | sticky); |
michael@0 | 168 | value *= factor; |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | return value; |
michael@0 | 172 | } |
michael@0 | 173 | |
michael@0 | 174 | double |
michael@0 | 175 | js::ParseDecimalNumber(const JS::TwoByteChars chars) |
michael@0 | 176 | { |
michael@0 | 177 | MOZ_ASSERT(chars.length() > 0); |
michael@0 | 178 | uint64_t dec = 0; |
michael@0 | 179 | RangedPtr<jschar> s = chars.start(), end = chars.end(); |
michael@0 | 180 | do { |
michael@0 | 181 | jschar c = *s; |
michael@0 | 182 | MOZ_ASSERT('0' <= c && c <= '9'); |
michael@0 | 183 | uint8_t digit = c - '0'; |
michael@0 | 184 | uint64_t next = dec * 10 + digit; |
michael@0 | 185 | MOZ_ASSERT(next < DOUBLE_INTEGRAL_PRECISION_LIMIT, |
michael@0 | 186 | "next value won't be an integrally-precise double"); |
michael@0 | 187 | dec = next; |
michael@0 | 188 | } while (++s < end); |
michael@0 | 189 | return static_cast<double>(dec); |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | bool |
michael@0 | 193 | js::GetPrefixInteger(ThreadSafeContext *cx, const jschar *start, const jschar *end, int base, |
michael@0 | 194 | const jschar **endp, double *dp) |
michael@0 | 195 | { |
michael@0 | 196 | JS_ASSERT(start <= end); |
michael@0 | 197 | JS_ASSERT(2 <= base && base <= 36); |
michael@0 | 198 | |
michael@0 | 199 | const jschar *s = start; |
michael@0 | 200 | double d = 0.0; |
michael@0 | 201 | for (; s < end; s++) { |
michael@0 | 202 | int digit; |
michael@0 | 203 | jschar c = *s; |
michael@0 | 204 | if ('0' <= c && c <= '9') |
michael@0 | 205 | digit = c - '0'; |
michael@0 | 206 | else if ('a' <= c && c <= 'z') |
michael@0 | 207 | digit = c - 'a' + 10; |
michael@0 | 208 | else if ('A' <= c && c <= 'Z') |
michael@0 | 209 | digit = c - 'A' + 10; |
michael@0 | 210 | else |
michael@0 | 211 | break; |
michael@0 | 212 | if (digit >= base) |
michael@0 | 213 | break; |
michael@0 | 214 | d = d * base + digit; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | *endp = s; |
michael@0 | 218 | *dp = d; |
michael@0 | 219 | |
michael@0 | 220 | /* If we haven't reached the limit of integer precision, we're done. */ |
michael@0 | 221 | if (d < DOUBLE_INTEGRAL_PRECISION_LIMIT) |
michael@0 | 222 | return true; |
michael@0 | 223 | |
michael@0 | 224 | /* |
michael@0 | 225 | * Otherwise compute the correct integer from the prefix of valid digits |
michael@0 | 226 | * if we're computing for base ten or a power of two. Don't worry about |
michael@0 | 227 | * other bases; see 15.1.2.2 step 13. |
michael@0 | 228 | */ |
michael@0 | 229 | if (base == 10) |
michael@0 | 230 | return ComputeAccurateDecimalInteger(cx, start, s, dp); |
michael@0 | 231 | if ((base & (base - 1)) == 0) |
michael@0 | 232 | *dp = ComputeAccurateBinaryBaseInteger(start, s, base); |
michael@0 | 233 | |
michael@0 | 234 | return true; |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | bool |
michael@0 | 238 | js::GetDecimalInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, double *dp) |
michael@0 | 239 | { |
michael@0 | 240 | JS_ASSERT(start <= end); |
michael@0 | 241 | |
michael@0 | 242 | const jschar *s = start; |
michael@0 | 243 | double d = 0.0; |
michael@0 | 244 | for (; s < end; s++) { |
michael@0 | 245 | jschar c = *s; |
michael@0 | 246 | JS_ASSERT('0' <= c && c <= '9'); |
michael@0 | 247 | int digit = c - '0'; |
michael@0 | 248 | d = d * 10 + digit; |
michael@0 | 249 | } |
michael@0 | 250 | |
michael@0 | 251 | *dp = d; |
michael@0 | 252 | |
michael@0 | 253 | // If we haven't reached the limit of integer precision, we're done. |
michael@0 | 254 | if (d < DOUBLE_INTEGRAL_PRECISION_LIMIT) |
michael@0 | 255 | return true; |
michael@0 | 256 | |
michael@0 | 257 | // Otherwise compute the correct integer from the prefix of valid digits. |
michael@0 | 258 | return ComputeAccurateDecimalInteger(cx, start, s, dp); |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | static bool |
michael@0 | 262 | num_isNaN(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 263 | { |
michael@0 | 264 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 265 | |
michael@0 | 266 | if (args.length() == 0) { |
michael@0 | 267 | args.rval().setBoolean(true); |
michael@0 | 268 | return true; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | double x; |
michael@0 | 272 | if (!ToNumber(cx, args[0], &x)) |
michael@0 | 273 | return false; |
michael@0 | 274 | |
michael@0 | 275 | args.rval().setBoolean(mozilla::IsNaN(x)); |
michael@0 | 276 | return true; |
michael@0 | 277 | } |
michael@0 | 278 | |
michael@0 | 279 | static bool |
michael@0 | 280 | num_isFinite(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 281 | { |
michael@0 | 282 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 283 | |
michael@0 | 284 | if (args.length() == 0) { |
michael@0 | 285 | args.rval().setBoolean(false); |
michael@0 | 286 | return true; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | double x; |
michael@0 | 290 | if (!ToNumber(cx, args[0], &x)) |
michael@0 | 291 | return false; |
michael@0 | 292 | |
michael@0 | 293 | args.rval().setBoolean(mozilla::IsFinite(x)); |
michael@0 | 294 | return true; |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | static bool |
michael@0 | 298 | num_parseFloat(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 299 | { |
michael@0 | 300 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 301 | |
michael@0 | 302 | if (args.length() == 0) { |
michael@0 | 303 | args.rval().setNaN(); |
michael@0 | 304 | return true; |
michael@0 | 305 | } |
michael@0 | 306 | JSString *str = ToString<CanGC>(cx, args[0]); |
michael@0 | 307 | if (!str) |
michael@0 | 308 | return false; |
michael@0 | 309 | const jschar *bp = str->getChars(cx); |
michael@0 | 310 | if (!bp) |
michael@0 | 311 | return false; |
michael@0 | 312 | const jschar *end = bp + str->length(); |
michael@0 | 313 | const jschar *ep; |
michael@0 | 314 | double d; |
michael@0 | 315 | if (!js_strtod(cx, bp, end, &ep, &d)) |
michael@0 | 316 | return false; |
michael@0 | 317 | if (ep == bp) { |
michael@0 | 318 | args.rval().setNaN(); |
michael@0 | 319 | return true; |
michael@0 | 320 | } |
michael@0 | 321 | args.rval().setDouble(d); |
michael@0 | 322 | return true; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | /* ES5 15.1.2.2. */ |
michael@0 | 326 | bool |
michael@0 | 327 | js::num_parseInt(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 328 | { |
michael@0 | 329 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 330 | |
michael@0 | 331 | /* Fast paths and exceptional cases. */ |
michael@0 | 332 | if (args.length() == 0) { |
michael@0 | 333 | args.rval().setNaN(); |
michael@0 | 334 | return true; |
michael@0 | 335 | } |
michael@0 | 336 | |
michael@0 | 337 | if (args.length() == 1 || |
michael@0 | 338 | (args[1].isInt32() && (args[1].toInt32() == 0 || args[1].toInt32() == 10))) { |
michael@0 | 339 | if (args[0].isInt32()) { |
michael@0 | 340 | args.rval().set(args[0]); |
michael@0 | 341 | return true; |
michael@0 | 342 | } |
michael@0 | 343 | |
michael@0 | 344 | /* |
michael@0 | 345 | * Step 1 is |inputString = ToString(string)|. When string >= |
michael@0 | 346 | * 1e21, ToString(string) is in the form "NeM". 'e' marks the end of |
michael@0 | 347 | * the word, which would mean the result of parseInt(string) should be |N|. |
michael@0 | 348 | * |
michael@0 | 349 | * To preserve this behaviour, we can't use the fast-path when string > |
michael@0 | 350 | * 1e21, or else the result would be |NeM|. |
michael@0 | 351 | * |
michael@0 | 352 | * The same goes for values smaller than 1.0e-6, because the string would be in |
michael@0 | 353 | * the form of "Ne-M". |
michael@0 | 354 | */ |
michael@0 | 355 | if (args[0].isDouble()) { |
michael@0 | 356 | double d = args[0].toDouble(); |
michael@0 | 357 | if (1.0e-6 < d && d < 1.0e21) { |
michael@0 | 358 | args.rval().setNumber(floor(d)); |
michael@0 | 359 | return true; |
michael@0 | 360 | } |
michael@0 | 361 | if (-1.0e21 < d && d < -1.0e-6) { |
michael@0 | 362 | args.rval().setNumber(-floor(-d)); |
michael@0 | 363 | return true; |
michael@0 | 364 | } |
michael@0 | 365 | if (d == 0.0) { |
michael@0 | 366 | args.rval().setInt32(0); |
michael@0 | 367 | return true; |
michael@0 | 368 | } |
michael@0 | 369 | } |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | /* Step 1. */ |
michael@0 | 373 | RootedString inputString(cx, ToString<CanGC>(cx, args[0])); |
michael@0 | 374 | if (!inputString) |
michael@0 | 375 | return false; |
michael@0 | 376 | args[0].setString(inputString); |
michael@0 | 377 | |
michael@0 | 378 | /* Steps 6-9. */ |
michael@0 | 379 | bool stripPrefix = true; |
michael@0 | 380 | int32_t radix; |
michael@0 | 381 | if (!args.hasDefined(1)) { |
michael@0 | 382 | radix = 10; |
michael@0 | 383 | } else { |
michael@0 | 384 | if (!ToInt32(cx, args[1], &radix)) |
michael@0 | 385 | return false; |
michael@0 | 386 | if (radix == 0) { |
michael@0 | 387 | radix = 10; |
michael@0 | 388 | } else { |
michael@0 | 389 | if (radix < 2 || radix > 36) { |
michael@0 | 390 | args.rval().setNaN(); |
michael@0 | 391 | return true; |
michael@0 | 392 | } |
michael@0 | 393 | if (radix != 16) |
michael@0 | 394 | stripPrefix = false; |
michael@0 | 395 | } |
michael@0 | 396 | } |
michael@0 | 397 | |
michael@0 | 398 | /* Step 2. */ |
michael@0 | 399 | const jschar *s; |
michael@0 | 400 | const jschar *end; |
michael@0 | 401 | { |
michael@0 | 402 | const jschar *ws = inputString->getChars(cx); |
michael@0 | 403 | if (!ws) |
michael@0 | 404 | return false; |
michael@0 | 405 | end = ws + inputString->length(); |
michael@0 | 406 | s = SkipSpace(ws, end); |
michael@0 | 407 | |
michael@0 | 408 | MOZ_ASSERT(ws <= s); |
michael@0 | 409 | MOZ_ASSERT(s <= end); |
michael@0 | 410 | } |
michael@0 | 411 | |
michael@0 | 412 | /* Steps 3-4. */ |
michael@0 | 413 | bool negative = (s != end && s[0] == '-'); |
michael@0 | 414 | |
michael@0 | 415 | /* Step 5. */ |
michael@0 | 416 | if (s != end && (s[0] == '-' || s[0] == '+')) |
michael@0 | 417 | s++; |
michael@0 | 418 | |
michael@0 | 419 | /* Step 10. */ |
michael@0 | 420 | if (stripPrefix) { |
michael@0 | 421 | if (end - s >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { |
michael@0 | 422 | s += 2; |
michael@0 | 423 | radix = 16; |
michael@0 | 424 | } |
michael@0 | 425 | } |
michael@0 | 426 | |
michael@0 | 427 | /* Steps 11-15. */ |
michael@0 | 428 | const jschar *actualEnd; |
michael@0 | 429 | double number; |
michael@0 | 430 | if (!GetPrefixInteger(cx, s, end, radix, &actualEnd, &number)) |
michael@0 | 431 | return false; |
michael@0 | 432 | if (s == actualEnd) |
michael@0 | 433 | args.rval().setNaN(); |
michael@0 | 434 | else |
michael@0 | 435 | args.rval().setNumber(negative ? -number : number); |
michael@0 | 436 | return true; |
michael@0 | 437 | } |
michael@0 | 438 | |
michael@0 | 439 | static const JSFunctionSpec number_functions[] = { |
michael@0 | 440 | JS_FN(js_isNaN_str, num_isNaN, 1,0), |
michael@0 | 441 | JS_FN(js_isFinite_str, num_isFinite, 1,0), |
michael@0 | 442 | JS_FN(js_parseFloat_str, num_parseFloat, 1,0), |
michael@0 | 443 | JS_FN(js_parseInt_str, num_parseInt, 2,0), |
michael@0 | 444 | JS_FS_END |
michael@0 | 445 | }; |
michael@0 | 446 | |
michael@0 | 447 | const Class NumberObject::class_ = { |
michael@0 | 448 | js_Number_str, |
michael@0 | 449 | JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number), |
michael@0 | 450 | JS_PropertyStub, /* addProperty */ |
michael@0 | 451 | JS_DeletePropertyStub, /* delProperty */ |
michael@0 | 452 | JS_PropertyStub, /* getProperty */ |
michael@0 | 453 | JS_StrictPropertyStub, /* setProperty */ |
michael@0 | 454 | JS_EnumerateStub, |
michael@0 | 455 | JS_ResolveStub, |
michael@0 | 456 | JS_ConvertStub |
michael@0 | 457 | }; |
michael@0 | 458 | |
michael@0 | 459 | static bool |
michael@0 | 460 | Number(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 461 | { |
michael@0 | 462 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 463 | |
michael@0 | 464 | /* Sample JS_CALLEE before clobbering. */ |
michael@0 | 465 | bool isConstructing = args.isConstructing(); |
michael@0 | 466 | |
michael@0 | 467 | if (args.length() > 0) { |
michael@0 | 468 | if (!ToNumber(cx, args[0])) |
michael@0 | 469 | return false; |
michael@0 | 470 | args.rval().set(args[0]); |
michael@0 | 471 | } else { |
michael@0 | 472 | args.rval().setInt32(0); |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | if (!isConstructing) |
michael@0 | 476 | return true; |
michael@0 | 477 | |
michael@0 | 478 | JSObject *obj = NumberObject::create(cx, args.rval().toNumber()); |
michael@0 | 479 | if (!obj) |
michael@0 | 480 | return false; |
michael@0 | 481 | args.rval().setObject(*obj); |
michael@0 | 482 | return true; |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | MOZ_ALWAYS_INLINE bool |
michael@0 | 486 | IsNumber(HandleValue v) |
michael@0 | 487 | { |
michael@0 | 488 | return v.isNumber() || (v.isObject() && v.toObject().is<NumberObject>()); |
michael@0 | 489 | } |
michael@0 | 490 | |
michael@0 | 491 | static inline double |
michael@0 | 492 | Extract(const Value &v) |
michael@0 | 493 | { |
michael@0 | 494 | if (v.isNumber()) |
michael@0 | 495 | return v.toNumber(); |
michael@0 | 496 | return v.toObject().as<NumberObject>().unbox(); |
michael@0 | 497 | } |
michael@0 | 498 | |
michael@0 | 499 | #if JS_HAS_TOSOURCE |
michael@0 | 500 | MOZ_ALWAYS_INLINE bool |
michael@0 | 501 | num_toSource_impl(JSContext *cx, CallArgs args) |
michael@0 | 502 | { |
michael@0 | 503 | double d = Extract(args.thisv()); |
michael@0 | 504 | |
michael@0 | 505 | StringBuffer sb(cx); |
michael@0 | 506 | if (!sb.append("(new Number(") || |
michael@0 | 507 | !NumberValueToStringBuffer(cx, NumberValue(d), sb) || |
michael@0 | 508 | !sb.append("))")) |
michael@0 | 509 | { |
michael@0 | 510 | return false; |
michael@0 | 511 | } |
michael@0 | 512 | |
michael@0 | 513 | JSString *str = sb.finishString(); |
michael@0 | 514 | if (!str) |
michael@0 | 515 | return false; |
michael@0 | 516 | args.rval().setString(str); |
michael@0 | 517 | return true; |
michael@0 | 518 | } |
michael@0 | 519 | |
michael@0 | 520 | static bool |
michael@0 | 521 | num_toSource(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 522 | { |
michael@0 | 523 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 524 | return CallNonGenericMethod<IsNumber, num_toSource_impl>(cx, args); |
michael@0 | 525 | } |
michael@0 | 526 | #endif |
michael@0 | 527 | |
michael@0 | 528 | ToCStringBuf::ToCStringBuf() :dbuf(nullptr) |
michael@0 | 529 | { |
michael@0 | 530 | JS_STATIC_ASSERT(sbufSize >= DTOSTR_STANDARD_BUFFER_SIZE); |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | ToCStringBuf::~ToCStringBuf() |
michael@0 | 534 | { |
michael@0 | 535 | js_free(dbuf); |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | MOZ_ALWAYS_INLINE |
michael@0 | 539 | static JSFlatString * |
michael@0 | 540 | LookupDtoaCache(ThreadSafeContext *cx, double d) |
michael@0 | 541 | { |
michael@0 | 542 | if (!cx->isExclusiveContext()) |
michael@0 | 543 | return nullptr; |
michael@0 | 544 | |
michael@0 | 545 | if (JSCompartment *comp = cx->asExclusiveContext()->compartment()) { |
michael@0 | 546 | if (JSFlatString *str = comp->dtoaCache.lookup(10, d)) |
michael@0 | 547 | return str; |
michael@0 | 548 | } |
michael@0 | 549 | |
michael@0 | 550 | return nullptr; |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | MOZ_ALWAYS_INLINE |
michael@0 | 554 | static void |
michael@0 | 555 | CacheNumber(ThreadSafeContext *cx, double d, JSFlatString *str) |
michael@0 | 556 | { |
michael@0 | 557 | if (!cx->isExclusiveContext()) |
michael@0 | 558 | return; |
michael@0 | 559 | |
michael@0 | 560 | if (JSCompartment *comp = cx->asExclusiveContext()->compartment()) |
michael@0 | 561 | comp->dtoaCache.cache(10, d, str); |
michael@0 | 562 | } |
michael@0 | 563 | |
michael@0 | 564 | MOZ_ALWAYS_INLINE |
michael@0 | 565 | static JSFlatString * |
michael@0 | 566 | LookupInt32ToString(ThreadSafeContext *cx, int32_t si) |
michael@0 | 567 | { |
michael@0 | 568 | if (si >= 0 && StaticStrings::hasInt(si)) |
michael@0 | 569 | return cx->staticStrings().getInt(si); |
michael@0 | 570 | |
michael@0 | 571 | return LookupDtoaCache(cx, si); |
michael@0 | 572 | } |
michael@0 | 573 | |
michael@0 | 574 | template <typename T> |
michael@0 | 575 | MOZ_ALWAYS_INLINE |
michael@0 | 576 | static T * |
michael@0 | 577 | BackfillInt32InBuffer(int32_t si, T *buffer, size_t size, size_t *length) |
michael@0 | 578 | { |
michael@0 | 579 | uint32_t ui = Abs(si); |
michael@0 | 580 | JS_ASSERT_IF(si == INT32_MIN, ui == uint32_t(INT32_MAX) + 1); |
michael@0 | 581 | |
michael@0 | 582 | RangedPtr<T> end(buffer + size - 1, buffer, size); |
michael@0 | 583 | *end = '\0'; |
michael@0 | 584 | RangedPtr<T> start = BackfillIndexInCharBuffer(ui, end); |
michael@0 | 585 | if (si < 0) |
michael@0 | 586 | *--start = '-'; |
michael@0 | 587 | |
michael@0 | 588 | *length = end - start; |
michael@0 | 589 | return start.get(); |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | template <AllowGC allowGC> |
michael@0 | 593 | JSFlatString * |
michael@0 | 594 | js::Int32ToString(ThreadSafeContext *cx, int32_t si) |
michael@0 | 595 | { |
michael@0 | 596 | if (JSFlatString *str = LookupInt32ToString(cx, si)) |
michael@0 | 597 | return str; |
michael@0 | 598 | |
michael@0 | 599 | JSFatInlineString *str = js_NewGCFatInlineString<allowGC>(cx); |
michael@0 | 600 | if (!str) |
michael@0 | 601 | return nullptr; |
michael@0 | 602 | |
michael@0 | 603 | jschar buffer[JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1]; |
michael@0 | 604 | size_t length; |
michael@0 | 605 | jschar *start = BackfillInt32InBuffer(si, buffer, |
michael@0 | 606 | JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1, &length); |
michael@0 | 607 | |
michael@0 | 608 | PodCopy(str->init(length), start, length + 1); |
michael@0 | 609 | |
michael@0 | 610 | CacheNumber(cx, si, str); |
michael@0 | 611 | return str; |
michael@0 | 612 | } |
michael@0 | 613 | |
michael@0 | 614 | template JSFlatString * |
michael@0 | 615 | js::Int32ToString<CanGC>(ThreadSafeContext *cx, int32_t si); |
michael@0 | 616 | |
michael@0 | 617 | template JSFlatString * |
michael@0 | 618 | js::Int32ToString<NoGC>(ThreadSafeContext *cx, int32_t si); |
michael@0 | 619 | |
michael@0 | 620 | JSAtom * |
michael@0 | 621 | js::Int32ToAtom(ExclusiveContext *cx, int32_t si) |
michael@0 | 622 | { |
michael@0 | 623 | if (JSFlatString *str = LookupInt32ToString(cx, si)) |
michael@0 | 624 | return js::AtomizeString(cx, str); |
michael@0 | 625 | |
michael@0 | 626 | char buffer[JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1]; |
michael@0 | 627 | size_t length; |
michael@0 | 628 | char *start = BackfillInt32InBuffer(si, buffer, JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1, &length); |
michael@0 | 629 | |
michael@0 | 630 | JSAtom *atom = Atomize(cx, start, length); |
michael@0 | 631 | if (!atom) |
michael@0 | 632 | return nullptr; |
michael@0 | 633 | |
michael@0 | 634 | CacheNumber(cx, si, atom); |
michael@0 | 635 | return atom; |
michael@0 | 636 | } |
michael@0 | 637 | |
michael@0 | 638 | /* Returns a non-nullptr pointer to inside cbuf. */ |
michael@0 | 639 | static char * |
michael@0 | 640 | Int32ToCString(ToCStringBuf *cbuf, int32_t i, size_t *len, int base = 10) |
michael@0 | 641 | { |
michael@0 | 642 | uint32_t u = Abs(i); |
michael@0 | 643 | |
michael@0 | 644 | RangedPtr<char> cp(cbuf->sbuf + ToCStringBuf::sbufSize - 1, cbuf->sbuf, ToCStringBuf::sbufSize); |
michael@0 | 645 | char *end = cp.get(); |
michael@0 | 646 | *cp = '\0'; |
michael@0 | 647 | |
michael@0 | 648 | /* Build the string from behind. */ |
michael@0 | 649 | switch (base) { |
michael@0 | 650 | case 10: |
michael@0 | 651 | cp = BackfillIndexInCharBuffer(u, cp); |
michael@0 | 652 | break; |
michael@0 | 653 | case 16: |
michael@0 | 654 | do { |
michael@0 | 655 | unsigned newu = u / 16; |
michael@0 | 656 | *--cp = "0123456789abcdef"[u - newu * 16]; |
michael@0 | 657 | u = newu; |
michael@0 | 658 | } while (u != 0); |
michael@0 | 659 | break; |
michael@0 | 660 | default: |
michael@0 | 661 | JS_ASSERT(base >= 2 && base <= 36); |
michael@0 | 662 | do { |
michael@0 | 663 | unsigned newu = u / base; |
michael@0 | 664 | *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[u - newu * base]; |
michael@0 | 665 | u = newu; |
michael@0 | 666 | } while (u != 0); |
michael@0 | 667 | break; |
michael@0 | 668 | } |
michael@0 | 669 | if (i < 0) |
michael@0 | 670 | *--cp = '-'; |
michael@0 | 671 | |
michael@0 | 672 | *len = end - cp.get(); |
michael@0 | 673 | return cp.get(); |
michael@0 | 674 | } |
michael@0 | 675 | |
michael@0 | 676 | template <AllowGC allowGC> |
michael@0 | 677 | static JSString * JS_FASTCALL |
michael@0 | 678 | js_NumberToStringWithBase(ThreadSafeContext *cx, double d, int base); |
michael@0 | 679 | |
michael@0 | 680 | MOZ_ALWAYS_INLINE bool |
michael@0 | 681 | num_toString_impl(JSContext *cx, CallArgs args) |
michael@0 | 682 | { |
michael@0 | 683 | JS_ASSERT(IsNumber(args.thisv())); |
michael@0 | 684 | |
michael@0 | 685 | double d = Extract(args.thisv()); |
michael@0 | 686 | |
michael@0 | 687 | int32_t base = 10; |
michael@0 | 688 | if (args.hasDefined(0)) { |
michael@0 | 689 | double d2; |
michael@0 | 690 | if (!ToInteger(cx, args[0], &d2)) |
michael@0 | 691 | return false; |
michael@0 | 692 | |
michael@0 | 693 | if (d2 < 2 || d2 > 36) { |
michael@0 | 694 | JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_RADIX); |
michael@0 | 695 | return false; |
michael@0 | 696 | } |
michael@0 | 697 | |
michael@0 | 698 | base = int32_t(d2); |
michael@0 | 699 | } |
michael@0 | 700 | JSString *str = js_NumberToStringWithBase<CanGC>(cx, d, base); |
michael@0 | 701 | if (!str) { |
michael@0 | 702 | JS_ReportOutOfMemory(cx); |
michael@0 | 703 | return false; |
michael@0 | 704 | } |
michael@0 | 705 | args.rval().setString(str); |
michael@0 | 706 | return true; |
michael@0 | 707 | } |
michael@0 | 708 | |
michael@0 | 709 | bool |
michael@0 | 710 | js_num_toString(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 711 | { |
michael@0 | 712 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 713 | return CallNonGenericMethod<IsNumber, num_toString_impl>(cx, args); |
michael@0 | 714 | } |
michael@0 | 715 | |
michael@0 | 716 | #if !EXPOSE_INTL_API |
michael@0 | 717 | MOZ_ALWAYS_INLINE bool |
michael@0 | 718 | num_toLocaleString_impl(JSContext *cx, CallArgs args) |
michael@0 | 719 | { |
michael@0 | 720 | JS_ASSERT(IsNumber(args.thisv())); |
michael@0 | 721 | |
michael@0 | 722 | double d = Extract(args.thisv()); |
michael@0 | 723 | |
michael@0 | 724 | Rooted<JSString*> str(cx, js_NumberToStringWithBase<CanGC>(cx, d, 10)); |
michael@0 | 725 | if (!str) { |
michael@0 | 726 | JS_ReportOutOfMemory(cx); |
michael@0 | 727 | return false; |
michael@0 | 728 | } |
michael@0 | 729 | |
michael@0 | 730 | /* |
michael@0 | 731 | * Create the string, move back to bytes to make string twiddling |
michael@0 | 732 | * a bit easier and so we can insert platform charset seperators. |
michael@0 | 733 | */ |
michael@0 | 734 | JSAutoByteString numBytes(cx, str); |
michael@0 | 735 | if (!numBytes) |
michael@0 | 736 | return false; |
michael@0 | 737 | const char *num = numBytes.ptr(); |
michael@0 | 738 | if (!num) |
michael@0 | 739 | return false; |
michael@0 | 740 | |
michael@0 | 741 | /* |
michael@0 | 742 | * Find the first non-integer value, whether it be a letter as in |
michael@0 | 743 | * 'Infinity', a decimal point, or an 'e' from exponential notation. |
michael@0 | 744 | */ |
michael@0 | 745 | const char *nint = num; |
michael@0 | 746 | if (*nint == '-') |
michael@0 | 747 | nint++; |
michael@0 | 748 | while (*nint >= '0' && *nint <= '9') |
michael@0 | 749 | nint++; |
michael@0 | 750 | int digits = nint - num; |
michael@0 | 751 | const char *end = num + digits; |
michael@0 | 752 | if (!digits) { |
michael@0 | 753 | args.rval().setString(str); |
michael@0 | 754 | return true; |
michael@0 | 755 | } |
michael@0 | 756 | |
michael@0 | 757 | JSRuntime *rt = cx->runtime(); |
michael@0 | 758 | size_t thousandsLength = strlen(rt->thousandsSeparator); |
michael@0 | 759 | size_t decimalLength = strlen(rt->decimalSeparator); |
michael@0 | 760 | |
michael@0 | 761 | /* Figure out how long resulting string will be. */ |
michael@0 | 762 | int buflen = strlen(num); |
michael@0 | 763 | if (*nint == '.') |
michael@0 | 764 | buflen += decimalLength - 1; /* -1 to account for existing '.' */ |
michael@0 | 765 | |
michael@0 | 766 | const char *numGrouping; |
michael@0 | 767 | const char *tmpGroup; |
michael@0 | 768 | numGrouping = tmpGroup = rt->numGrouping; |
michael@0 | 769 | int remainder = digits; |
michael@0 | 770 | if (*num == '-') |
michael@0 | 771 | remainder--; |
michael@0 | 772 | |
michael@0 | 773 | while (*tmpGroup != CHAR_MAX && *tmpGroup != '\0') { |
michael@0 | 774 | if (*tmpGroup >= remainder) |
michael@0 | 775 | break; |
michael@0 | 776 | buflen += thousandsLength; |
michael@0 | 777 | remainder -= *tmpGroup; |
michael@0 | 778 | tmpGroup++; |
michael@0 | 779 | } |
michael@0 | 780 | |
michael@0 | 781 | int nrepeat; |
michael@0 | 782 | if (*tmpGroup == '\0' && *numGrouping != '\0') { |
michael@0 | 783 | nrepeat = (remainder - 1) / tmpGroup[-1]; |
michael@0 | 784 | buflen += thousandsLength * nrepeat; |
michael@0 | 785 | remainder -= nrepeat * tmpGroup[-1]; |
michael@0 | 786 | } else { |
michael@0 | 787 | nrepeat = 0; |
michael@0 | 788 | } |
michael@0 | 789 | tmpGroup--; |
michael@0 | 790 | |
michael@0 | 791 | char *buf = cx->pod_malloc<char>(buflen + 1); |
michael@0 | 792 | if (!buf) |
michael@0 | 793 | return false; |
michael@0 | 794 | |
michael@0 | 795 | char *tmpDest = buf; |
michael@0 | 796 | const char *tmpSrc = num; |
michael@0 | 797 | |
michael@0 | 798 | while (*tmpSrc == '-' || remainder--) { |
michael@0 | 799 | JS_ASSERT(tmpDest - buf < buflen); |
michael@0 | 800 | *tmpDest++ = *tmpSrc++; |
michael@0 | 801 | } |
michael@0 | 802 | while (tmpSrc < end) { |
michael@0 | 803 | JS_ASSERT(tmpDest - buf + ptrdiff_t(thousandsLength) <= buflen); |
michael@0 | 804 | strcpy(tmpDest, rt->thousandsSeparator); |
michael@0 | 805 | tmpDest += thousandsLength; |
michael@0 | 806 | JS_ASSERT(tmpDest - buf + *tmpGroup <= buflen); |
michael@0 | 807 | js_memcpy(tmpDest, tmpSrc, *tmpGroup); |
michael@0 | 808 | tmpDest += *tmpGroup; |
michael@0 | 809 | tmpSrc += *tmpGroup; |
michael@0 | 810 | if (--nrepeat < 0) |
michael@0 | 811 | tmpGroup--; |
michael@0 | 812 | } |
michael@0 | 813 | |
michael@0 | 814 | if (*nint == '.') { |
michael@0 | 815 | JS_ASSERT(tmpDest - buf + ptrdiff_t(decimalLength) <= buflen); |
michael@0 | 816 | strcpy(tmpDest, rt->decimalSeparator); |
michael@0 | 817 | tmpDest += decimalLength; |
michael@0 | 818 | JS_ASSERT(tmpDest - buf + ptrdiff_t(strlen(nint + 1)) <= buflen); |
michael@0 | 819 | strcpy(tmpDest, nint + 1); |
michael@0 | 820 | } else { |
michael@0 | 821 | JS_ASSERT(tmpDest - buf + ptrdiff_t(strlen(nint)) <= buflen); |
michael@0 | 822 | strcpy(tmpDest, nint); |
michael@0 | 823 | } |
michael@0 | 824 | |
michael@0 | 825 | if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode) { |
michael@0 | 826 | Rooted<Value> v(cx, StringValue(str)); |
michael@0 | 827 | bool ok = !!cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, &v); |
michael@0 | 828 | if (ok) |
michael@0 | 829 | args.rval().set(v); |
michael@0 | 830 | js_free(buf); |
michael@0 | 831 | return ok; |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | str = js_NewStringCopyN<CanGC>(cx, buf, buflen); |
michael@0 | 835 | js_free(buf); |
michael@0 | 836 | if (!str) |
michael@0 | 837 | return false; |
michael@0 | 838 | |
michael@0 | 839 | args.rval().setString(str); |
michael@0 | 840 | return true; |
michael@0 | 841 | } |
michael@0 | 842 | |
michael@0 | 843 | static bool |
michael@0 | 844 | num_toLocaleString(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 845 | { |
michael@0 | 846 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 847 | return CallNonGenericMethod<IsNumber, num_toLocaleString_impl>(cx, args); |
michael@0 | 848 | } |
michael@0 | 849 | #endif /* !EXPOSE_INTL_API */ |
michael@0 | 850 | |
michael@0 | 851 | MOZ_ALWAYS_INLINE bool |
michael@0 | 852 | num_valueOf_impl(JSContext *cx, CallArgs args) |
michael@0 | 853 | { |
michael@0 | 854 | JS_ASSERT(IsNumber(args.thisv())); |
michael@0 | 855 | args.rval().setNumber(Extract(args.thisv())); |
michael@0 | 856 | return true; |
michael@0 | 857 | } |
michael@0 | 858 | |
michael@0 | 859 | bool |
michael@0 | 860 | js_num_valueOf(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 861 | { |
michael@0 | 862 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 863 | return CallNonGenericMethod<IsNumber, num_valueOf_impl>(cx, args); |
michael@0 | 864 | } |
michael@0 | 865 | |
michael@0 | 866 | static const unsigned MAX_PRECISION = 100; |
michael@0 | 867 | |
michael@0 | 868 | static bool |
michael@0 | 869 | ComputePrecisionInRange(JSContext *cx, int minPrecision, int maxPrecision, HandleValue v, |
michael@0 | 870 | int *precision) |
michael@0 | 871 | { |
michael@0 | 872 | double prec; |
michael@0 | 873 | if (!ToInteger(cx, v, &prec)) |
michael@0 | 874 | return false; |
michael@0 | 875 | if (minPrecision <= prec && prec <= maxPrecision) { |
michael@0 | 876 | *precision = int(prec); |
michael@0 | 877 | return true; |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | ToCStringBuf cbuf; |
michael@0 | 881 | if (char *numStr = NumberToCString(cx, &cbuf, prec, 10)) |
michael@0 | 882 | JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PRECISION_RANGE, numStr); |
michael@0 | 883 | return false; |
michael@0 | 884 | } |
michael@0 | 885 | |
michael@0 | 886 | static bool |
michael@0 | 887 | DToStrResult(JSContext *cx, double d, JSDToStrMode mode, int precision, CallArgs args) |
michael@0 | 888 | { |
michael@0 | 889 | char buf[DTOSTR_VARIABLE_BUFFER_SIZE(MAX_PRECISION + 1)]; |
michael@0 | 890 | char *numStr = js_dtostr(cx->mainThread().dtoaState, buf, sizeof buf, mode, precision, d); |
michael@0 | 891 | if (!numStr) { |
michael@0 | 892 | JS_ReportOutOfMemory(cx); |
michael@0 | 893 | return false; |
michael@0 | 894 | } |
michael@0 | 895 | JSString *str = js_NewStringCopyZ<CanGC>(cx, numStr); |
michael@0 | 896 | if (!str) |
michael@0 | 897 | return false; |
michael@0 | 898 | args.rval().setString(str); |
michael@0 | 899 | return true; |
michael@0 | 900 | } |
michael@0 | 901 | |
michael@0 | 902 | /* |
michael@0 | 903 | * In the following three implementations, we allow a larger range of precision |
michael@0 | 904 | * than ECMA requires; this is permitted by ECMA-262. |
michael@0 | 905 | */ |
michael@0 | 906 | MOZ_ALWAYS_INLINE bool |
michael@0 | 907 | num_toFixed_impl(JSContext *cx, CallArgs args) |
michael@0 | 908 | { |
michael@0 | 909 | JS_ASSERT(IsNumber(args.thisv())); |
michael@0 | 910 | |
michael@0 | 911 | int precision; |
michael@0 | 912 | if (args.length() == 0) { |
michael@0 | 913 | precision = 0; |
michael@0 | 914 | } else { |
michael@0 | 915 | if (!ComputePrecisionInRange(cx, -20, MAX_PRECISION, args[0], &precision)) |
michael@0 | 916 | return false; |
michael@0 | 917 | } |
michael@0 | 918 | |
michael@0 | 919 | return DToStrResult(cx, Extract(args.thisv()), DTOSTR_FIXED, precision, args); |
michael@0 | 920 | } |
michael@0 | 921 | |
michael@0 | 922 | static bool |
michael@0 | 923 | num_toFixed(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 924 | { |
michael@0 | 925 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 926 | return CallNonGenericMethod<IsNumber, num_toFixed_impl>(cx, args); |
michael@0 | 927 | } |
michael@0 | 928 | |
michael@0 | 929 | MOZ_ALWAYS_INLINE bool |
michael@0 | 930 | num_toExponential_impl(JSContext *cx, CallArgs args) |
michael@0 | 931 | { |
michael@0 | 932 | JS_ASSERT(IsNumber(args.thisv())); |
michael@0 | 933 | |
michael@0 | 934 | JSDToStrMode mode; |
michael@0 | 935 | int precision; |
michael@0 | 936 | if (!args.hasDefined(0)) { |
michael@0 | 937 | mode = DTOSTR_STANDARD_EXPONENTIAL; |
michael@0 | 938 | precision = 0; |
michael@0 | 939 | } else { |
michael@0 | 940 | mode = DTOSTR_EXPONENTIAL; |
michael@0 | 941 | if (!ComputePrecisionInRange(cx, 0, MAX_PRECISION, args[0], &precision)) |
michael@0 | 942 | return false; |
michael@0 | 943 | } |
michael@0 | 944 | |
michael@0 | 945 | return DToStrResult(cx, Extract(args.thisv()), mode, precision + 1, args); |
michael@0 | 946 | } |
michael@0 | 947 | |
michael@0 | 948 | static bool |
michael@0 | 949 | num_toExponential(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 950 | { |
michael@0 | 951 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 952 | return CallNonGenericMethod<IsNumber, num_toExponential_impl>(cx, args); |
michael@0 | 953 | } |
michael@0 | 954 | |
michael@0 | 955 | MOZ_ALWAYS_INLINE bool |
michael@0 | 956 | num_toPrecision_impl(JSContext *cx, CallArgs args) |
michael@0 | 957 | { |
michael@0 | 958 | JS_ASSERT(IsNumber(args.thisv())); |
michael@0 | 959 | |
michael@0 | 960 | double d = Extract(args.thisv()); |
michael@0 | 961 | |
michael@0 | 962 | if (!args.hasDefined(0)) { |
michael@0 | 963 | JSString *str = js_NumberToStringWithBase<CanGC>(cx, d, 10); |
michael@0 | 964 | if (!str) { |
michael@0 | 965 | JS_ReportOutOfMemory(cx); |
michael@0 | 966 | return false; |
michael@0 | 967 | } |
michael@0 | 968 | args.rval().setString(str); |
michael@0 | 969 | return true; |
michael@0 | 970 | } |
michael@0 | 971 | |
michael@0 | 972 | int precision; |
michael@0 | 973 | if (!ComputePrecisionInRange(cx, 1, MAX_PRECISION, args[0], &precision)) |
michael@0 | 974 | return false; |
michael@0 | 975 | |
michael@0 | 976 | return DToStrResult(cx, d, DTOSTR_PRECISION, precision, args); |
michael@0 | 977 | } |
michael@0 | 978 | |
michael@0 | 979 | static bool |
michael@0 | 980 | num_toPrecision(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 981 | { |
michael@0 | 982 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 983 | return CallNonGenericMethod<IsNumber, num_toPrecision_impl>(cx, args); |
michael@0 | 984 | } |
michael@0 | 985 | |
michael@0 | 986 | static const JSFunctionSpec number_methods[] = { |
michael@0 | 987 | #if JS_HAS_TOSOURCE |
michael@0 | 988 | JS_FN(js_toSource_str, num_toSource, 0, 0), |
michael@0 | 989 | #endif |
michael@0 | 990 | JS_FN(js_toString_str, js_num_toString, 1, 0), |
michael@0 | 991 | #if EXPOSE_INTL_API |
michael@0 | 992 | JS_SELF_HOSTED_FN(js_toLocaleString_str, "Number_toLocaleString", 0,0), |
michael@0 | 993 | #else |
michael@0 | 994 | JS_FN(js_toLocaleString_str, num_toLocaleString, 0,0), |
michael@0 | 995 | #endif |
michael@0 | 996 | JS_FN(js_valueOf_str, js_num_valueOf, 0, 0), |
michael@0 | 997 | JS_FN("toFixed", num_toFixed, 1, 0), |
michael@0 | 998 | JS_FN("toExponential", num_toExponential, 1, 0), |
michael@0 | 999 | JS_FN("toPrecision", num_toPrecision, 1, 0), |
michael@0 | 1000 | JS_FS_END |
michael@0 | 1001 | }; |
michael@0 | 1002 | |
michael@0 | 1003 | |
michael@0 | 1004 | // ES6 draft ES6 15.7.3.10 |
michael@0 | 1005 | static bool |
michael@0 | 1006 | Number_isNaN(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 1007 | { |
michael@0 | 1008 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 1009 | if (args.length() < 1 || !args[0].isDouble()) { |
michael@0 | 1010 | args.rval().setBoolean(false); |
michael@0 | 1011 | return true; |
michael@0 | 1012 | } |
michael@0 | 1013 | args.rval().setBoolean(mozilla::IsNaN(args[0].toDouble())); |
michael@0 | 1014 | return true; |
michael@0 | 1015 | } |
michael@0 | 1016 | |
michael@0 | 1017 | // ES6 draft ES6 15.7.3.11 |
michael@0 | 1018 | static bool |
michael@0 | 1019 | Number_isFinite(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 1020 | { |
michael@0 | 1021 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 1022 | if (args.length() < 1 || !args[0].isNumber()) { |
michael@0 | 1023 | args.rval().setBoolean(false); |
michael@0 | 1024 | return true; |
michael@0 | 1025 | } |
michael@0 | 1026 | args.rval().setBoolean(args[0].isInt32() || |
michael@0 | 1027 | mozilla::IsFinite(args[0].toDouble())); |
michael@0 | 1028 | return true; |
michael@0 | 1029 | } |
michael@0 | 1030 | |
michael@0 | 1031 | // ES6 draft ES6 15.7.3.12 |
michael@0 | 1032 | static bool |
michael@0 | 1033 | Number_isInteger(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 1034 | { |
michael@0 | 1035 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 1036 | if (args.length() < 1 || !args[0].isNumber()) { |
michael@0 | 1037 | args.rval().setBoolean(false); |
michael@0 | 1038 | return true; |
michael@0 | 1039 | } |
michael@0 | 1040 | Value val = args[0]; |
michael@0 | 1041 | args.rval().setBoolean(val.isInt32() || |
michael@0 | 1042 | (mozilla::IsFinite(val.toDouble()) && |
michael@0 | 1043 | ToInteger(val.toDouble()) == val.toDouble())); |
michael@0 | 1044 | return true; |
michael@0 | 1045 | } |
michael@0 | 1046 | |
michael@0 | 1047 | // ES6 drafult ES6 15.7.3.13 |
michael@0 | 1048 | static bool |
michael@0 | 1049 | Number_toInteger(JSContext *cx, unsigned argc, Value *vp) |
michael@0 | 1050 | { |
michael@0 | 1051 | CallArgs args = CallArgsFromVp(argc, vp); |
michael@0 | 1052 | if (args.length() < 1) { |
michael@0 | 1053 | args.rval().setInt32(0); |
michael@0 | 1054 | return true; |
michael@0 | 1055 | } |
michael@0 | 1056 | double asint; |
michael@0 | 1057 | if (!ToInteger(cx, args[0], &asint)) |
michael@0 | 1058 | return false; |
michael@0 | 1059 | args.rval().setNumber(asint); |
michael@0 | 1060 | return true; |
michael@0 | 1061 | } |
michael@0 | 1062 | |
michael@0 | 1063 | |
michael@0 | 1064 | static const JSFunctionSpec number_static_methods[] = { |
michael@0 | 1065 | JS_FN("isFinite", Number_isFinite, 1, 0), |
michael@0 | 1066 | JS_FN("isInteger", Number_isInteger, 1, 0), |
michael@0 | 1067 | JS_FN("isNaN", Number_isNaN, 1, 0), |
michael@0 | 1068 | JS_FN("toInteger", Number_toInteger, 1, 0), |
michael@0 | 1069 | /* ES6 additions. */ |
michael@0 | 1070 | JS_FN("parseFloat", num_parseFloat, 1, 0), |
michael@0 | 1071 | JS_FN("parseInt", num_parseInt, 2, 0), |
michael@0 | 1072 | JS_FS_END |
michael@0 | 1073 | }; |
michael@0 | 1074 | |
michael@0 | 1075 | |
michael@0 | 1076 | /* NB: Keep this in synch with number_constants[]. */ |
michael@0 | 1077 | enum nc_slot { |
michael@0 | 1078 | NC_NaN, |
michael@0 | 1079 | NC_POSITIVE_INFINITY, |
michael@0 | 1080 | NC_NEGATIVE_INFINITY, |
michael@0 | 1081 | NC_MAX_VALUE, |
michael@0 | 1082 | NC_MIN_VALUE, |
michael@0 | 1083 | NC_MAX_SAFE_INTEGER, |
michael@0 | 1084 | NC_MIN_SAFE_INTEGER, |
michael@0 | 1085 | NC_EPSILON, |
michael@0 | 1086 | NC_LIMIT |
michael@0 | 1087 | }; |
michael@0 | 1088 | |
michael@0 | 1089 | /* |
michael@0 | 1090 | * Some to most C compilers forbid spelling these at compile time, or barf |
michael@0 | 1091 | * if you try, so all but MAX_VALUE are set up by InitRuntimeNumberState |
michael@0 | 1092 | * using union jsdpun. |
michael@0 | 1093 | */ |
michael@0 | 1094 | static JSConstDoubleSpec number_constants[] = { |
michael@0 | 1095 | {0, "NaN", 0,{0,0,0}}, |
michael@0 | 1096 | {0, "POSITIVE_INFINITY", 0,{0,0,0}}, |
michael@0 | 1097 | {0, "NEGATIVE_INFINITY", 0,{0,0,0}}, |
michael@0 | 1098 | {1.7976931348623157E+308, "MAX_VALUE", 0,{0,0,0}}, |
michael@0 | 1099 | {0, "MIN_VALUE", 0,{0,0,0}}, |
michael@0 | 1100 | /* ES6 (April 2014 draft) 20.1.2.6 */ |
michael@0 | 1101 | {9007199254740991, "MAX_SAFE_INTEGER", 0,{0,0,0}}, |
michael@0 | 1102 | /* ES6 (April 2014 draft) 20.1.2.10 */ |
michael@0 | 1103 | {-9007199254740991, "MIN_SAFE_INTEGER", 0,{0,0,0}}, |
michael@0 | 1104 | /* ES6 (May 2013 draft) 15.7.3.7 */ |
michael@0 | 1105 | {2.2204460492503130808472633361816e-16, "EPSILON", 0,{0,0,0}}, |
michael@0 | 1106 | {0,0,0,{0,0,0}} |
michael@0 | 1107 | }; |
michael@0 | 1108 | |
michael@0 | 1109 | #if (defined __GNUC__ && defined __i386__) || \ |
michael@0 | 1110 | (defined __SUNPRO_CC && defined __i386) |
michael@0 | 1111 | |
michael@0 | 1112 | /* |
michael@0 | 1113 | * Set the exception mask to mask all exceptions and set the FPU precision |
michael@0 | 1114 | * to 53 bit mantissa (64 bit doubles). |
michael@0 | 1115 | */ |
michael@0 | 1116 | static inline void FIX_FPU() { |
michael@0 | 1117 | short control; |
michael@0 | 1118 | asm("fstcw %0" : "=m" (control) : ); |
michael@0 | 1119 | control &= ~0x300; // Lower bits 8 and 9 (precision control). |
michael@0 | 1120 | control |= 0x2f3; // Raise bits 0-5 (exception masks) and 9 (64-bit precision). |
michael@0 | 1121 | asm("fldcw %0" : : "m" (control) ); |
michael@0 | 1122 | } |
michael@0 | 1123 | |
michael@0 | 1124 | #else |
michael@0 | 1125 | |
michael@0 | 1126 | #define FIX_FPU() ((void)0) |
michael@0 | 1127 | |
michael@0 | 1128 | #endif |
michael@0 | 1129 | |
michael@0 | 1130 | bool |
michael@0 | 1131 | js::InitRuntimeNumberState(JSRuntime *rt) |
michael@0 | 1132 | { |
michael@0 | 1133 | FIX_FPU(); |
michael@0 | 1134 | |
michael@0 | 1135 | /* |
michael@0 | 1136 | * Our NaN must be one particular canonical value, because we rely on NaN |
michael@0 | 1137 | * encoding for our value representation. See Value.h. |
michael@0 | 1138 | */ |
michael@0 | 1139 | number_constants[NC_NaN].dval = GenericNaN(); |
michael@0 | 1140 | |
michael@0 | 1141 | number_constants[NC_POSITIVE_INFINITY].dval = mozilla::PositiveInfinity<double>(); |
michael@0 | 1142 | number_constants[NC_NEGATIVE_INFINITY].dval = mozilla::NegativeInfinity<double>(); |
michael@0 | 1143 | |
michael@0 | 1144 | number_constants[NC_MIN_VALUE].dval = MinNumberValue<double>(); |
michael@0 | 1145 | |
michael@0 | 1146 | // XXX If EXPOSE_INTL_API becomes true all the time at some point, |
michael@0 | 1147 | // js::InitRuntimeNumberState is no longer fallible, and we should |
michael@0 | 1148 | // change its return type. |
michael@0 | 1149 | #if !EXPOSE_INTL_API |
michael@0 | 1150 | /* Copy locale-specific separators into the runtime strings. */ |
michael@0 | 1151 | const char *thousandsSeparator, *decimalPoint, *grouping; |
michael@0 | 1152 | #ifdef HAVE_LOCALECONV |
michael@0 | 1153 | struct lconv *locale = localeconv(); |
michael@0 | 1154 | thousandsSeparator = locale->thousands_sep; |
michael@0 | 1155 | decimalPoint = locale->decimal_point; |
michael@0 | 1156 | grouping = locale->grouping; |
michael@0 | 1157 | #else |
michael@0 | 1158 | thousandsSeparator = getenv("LOCALE_THOUSANDS_SEP"); |
michael@0 | 1159 | decimalPoint = getenv("LOCALE_DECIMAL_POINT"); |
michael@0 | 1160 | grouping = getenv("LOCALE_GROUPING"); |
michael@0 | 1161 | #endif |
michael@0 | 1162 | if (!thousandsSeparator) |
michael@0 | 1163 | thousandsSeparator = "'"; |
michael@0 | 1164 | if (!decimalPoint) |
michael@0 | 1165 | decimalPoint = "."; |
michael@0 | 1166 | if (!grouping) |
michael@0 | 1167 | grouping = "\3\0"; |
michael@0 | 1168 | |
michael@0 | 1169 | /* |
michael@0 | 1170 | * We use single malloc to get the memory for all separator and grouping |
michael@0 | 1171 | * strings. |
michael@0 | 1172 | */ |
michael@0 | 1173 | size_t thousandsSeparatorSize = strlen(thousandsSeparator) + 1; |
michael@0 | 1174 | size_t decimalPointSize = strlen(decimalPoint) + 1; |
michael@0 | 1175 | size_t groupingSize = strlen(grouping) + 1; |
michael@0 | 1176 | |
michael@0 | 1177 | char *storage = js_pod_malloc<char>(thousandsSeparatorSize + |
michael@0 | 1178 | decimalPointSize + |
michael@0 | 1179 | groupingSize); |
michael@0 | 1180 | if (!storage) |
michael@0 | 1181 | return false; |
michael@0 | 1182 | |
michael@0 | 1183 | js_memcpy(storage, thousandsSeparator, thousandsSeparatorSize); |
michael@0 | 1184 | rt->thousandsSeparator = storage; |
michael@0 | 1185 | storage += thousandsSeparatorSize; |
michael@0 | 1186 | |
michael@0 | 1187 | js_memcpy(storage, decimalPoint, decimalPointSize); |
michael@0 | 1188 | rt->decimalSeparator = storage; |
michael@0 | 1189 | storage += decimalPointSize; |
michael@0 | 1190 | |
michael@0 | 1191 | js_memcpy(storage, grouping, groupingSize); |
michael@0 | 1192 | rt->numGrouping = grouping; |
michael@0 | 1193 | #endif /* !EXPOSE_INTL_API */ |
michael@0 | 1194 | return true; |
michael@0 | 1195 | } |
michael@0 | 1196 | |
michael@0 | 1197 | #if !EXPOSE_INTL_API |
michael@0 | 1198 | void |
michael@0 | 1199 | js::FinishRuntimeNumberState(JSRuntime *rt) |
michael@0 | 1200 | { |
michael@0 | 1201 | /* |
michael@0 | 1202 | * The free also releases the memory for decimalSeparator and numGrouping |
michael@0 | 1203 | * strings. |
michael@0 | 1204 | */ |
michael@0 | 1205 | char *storage = const_cast<char *>(rt->thousandsSeparator); |
michael@0 | 1206 | js_free(storage); |
michael@0 | 1207 | } |
michael@0 | 1208 | #endif |
michael@0 | 1209 | |
michael@0 | 1210 | JSObject * |
michael@0 | 1211 | js_InitNumberClass(JSContext *cx, HandleObject obj) |
michael@0 | 1212 | { |
michael@0 | 1213 | JS_ASSERT(obj->isNative()); |
michael@0 | 1214 | |
michael@0 | 1215 | /* XXX must do at least once per new thread, so do it per JSContext... */ |
michael@0 | 1216 | FIX_FPU(); |
michael@0 | 1217 | |
michael@0 | 1218 | Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); |
michael@0 | 1219 | |
michael@0 | 1220 | RootedObject numberProto(cx, global->createBlankPrototype(cx, &NumberObject::class_)); |
michael@0 | 1221 | if (!numberProto) |
michael@0 | 1222 | return nullptr; |
michael@0 | 1223 | numberProto->as<NumberObject>().setPrimitiveValue(0); |
michael@0 | 1224 | |
michael@0 | 1225 | RootedFunction ctor(cx); |
michael@0 | 1226 | ctor = global->createConstructor(cx, Number, cx->names().Number, 1); |
michael@0 | 1227 | if (!ctor) |
michael@0 | 1228 | return nullptr; |
michael@0 | 1229 | |
michael@0 | 1230 | if (!LinkConstructorAndPrototype(cx, ctor, numberProto)) |
michael@0 | 1231 | return nullptr; |
michael@0 | 1232 | |
michael@0 | 1233 | /* Add numeric constants (MAX_VALUE, NaN, &c.) to the Number constructor. */ |
michael@0 | 1234 | if (!JS_DefineConstDoubles(cx, ctor, number_constants)) |
michael@0 | 1235 | return nullptr; |
michael@0 | 1236 | |
michael@0 | 1237 | if (!DefinePropertiesAndBrand(cx, ctor, nullptr, number_static_methods)) |
michael@0 | 1238 | return nullptr; |
michael@0 | 1239 | |
michael@0 | 1240 | if (!DefinePropertiesAndBrand(cx, numberProto, nullptr, number_methods)) |
michael@0 | 1241 | return nullptr; |
michael@0 | 1242 | |
michael@0 | 1243 | if (!JS_DefineFunctions(cx, global, number_functions)) |
michael@0 | 1244 | return nullptr; |
michael@0 | 1245 | |
michael@0 | 1246 | RootedValue valueNaN(cx, cx->runtime()->NaNValue); |
michael@0 | 1247 | RootedValue valueInfinity(cx, cx->runtime()->positiveInfinityValue); |
michael@0 | 1248 | |
michael@0 | 1249 | /* ES5 15.1.1.1, 15.1.1.2 */ |
michael@0 | 1250 | if (!DefineNativeProperty(cx, global, cx->names().NaN, valueNaN, |
michael@0 | 1251 | JS_PropertyStub, JS_StrictPropertyStub, |
michael@0 | 1252 | JSPROP_PERMANENT | JSPROP_READONLY) || |
michael@0 | 1253 | !DefineNativeProperty(cx, global, cx->names().Infinity, valueInfinity, |
michael@0 | 1254 | JS_PropertyStub, JS_StrictPropertyStub, |
michael@0 | 1255 | JSPROP_PERMANENT | JSPROP_READONLY)) |
michael@0 | 1256 | { |
michael@0 | 1257 | return nullptr; |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Number, ctor, numberProto)) |
michael@0 | 1261 | return nullptr; |
michael@0 | 1262 | |
michael@0 | 1263 | return numberProto; |
michael@0 | 1264 | } |
michael@0 | 1265 | |
michael@0 | 1266 | static char * |
michael@0 | 1267 | FracNumberToCString(ThreadSafeContext *cx, ToCStringBuf *cbuf, double d, int base = 10) |
michael@0 | 1268 | { |
michael@0 | 1269 | #ifdef DEBUG |
michael@0 | 1270 | { |
michael@0 | 1271 | int32_t _; |
michael@0 | 1272 | JS_ASSERT(!mozilla::NumberIsInt32(d, &_)); |
michael@0 | 1273 | } |
michael@0 | 1274 | #endif |
michael@0 | 1275 | |
michael@0 | 1276 | char* numStr; |
michael@0 | 1277 | if (base == 10) { |
michael@0 | 1278 | /* |
michael@0 | 1279 | * This is V8's implementation of the algorithm described in the |
michael@0 | 1280 | * following paper: |
michael@0 | 1281 | * |
michael@0 | 1282 | * Printing floating-point numbers quickly and accurately with integers. |
michael@0 | 1283 | * Florian Loitsch, PLDI 2010. |
michael@0 | 1284 | */ |
michael@0 | 1285 | const double_conversion::DoubleToStringConverter &converter |
michael@0 | 1286 | = double_conversion::DoubleToStringConverter::EcmaScriptConverter(); |
michael@0 | 1287 | double_conversion::StringBuilder builder(cbuf->sbuf, cbuf->sbufSize); |
michael@0 | 1288 | converter.ToShortest(d, &builder); |
michael@0 | 1289 | numStr = builder.Finalize(); |
michael@0 | 1290 | } else { |
michael@0 | 1291 | numStr = cbuf->dbuf = js_dtobasestr(cx->dtoaState(), base, d); |
michael@0 | 1292 | } |
michael@0 | 1293 | return numStr; |
michael@0 | 1294 | } |
michael@0 | 1295 | |
michael@0 | 1296 | char * |
michael@0 | 1297 | js::NumberToCString(JSContext *cx, ToCStringBuf *cbuf, double d, int base/* = 10*/) |
michael@0 | 1298 | { |
michael@0 | 1299 | int32_t i; |
michael@0 | 1300 | size_t len; |
michael@0 | 1301 | return mozilla::NumberIsInt32(d, &i) |
michael@0 | 1302 | ? Int32ToCString(cbuf, i, &len, base) |
michael@0 | 1303 | : FracNumberToCString(cx, cbuf, d, base); |
michael@0 | 1304 | } |
michael@0 | 1305 | |
michael@0 | 1306 | template <AllowGC allowGC> |
michael@0 | 1307 | static JSString * JS_FASTCALL |
michael@0 | 1308 | js_NumberToStringWithBase(ThreadSafeContext *cx, double d, int base) |
michael@0 | 1309 | { |
michael@0 | 1310 | ToCStringBuf cbuf; |
michael@0 | 1311 | char *numStr; |
michael@0 | 1312 | |
michael@0 | 1313 | /* |
michael@0 | 1314 | * Caller is responsible for error reporting. When called from trace, |
michael@0 | 1315 | * returning nullptr here will cause us to fall of trace and then retry |
michael@0 | 1316 | * from the interpreter (which will report the error). |
michael@0 | 1317 | */ |
michael@0 | 1318 | if (base < 2 || base > 36) |
michael@0 | 1319 | return nullptr; |
michael@0 | 1320 | |
michael@0 | 1321 | JSCompartment *comp = cx->isExclusiveContext() |
michael@0 | 1322 | ? cx->asExclusiveContext()->compartment() |
michael@0 | 1323 | : nullptr; |
michael@0 | 1324 | |
michael@0 | 1325 | int32_t i; |
michael@0 | 1326 | if (mozilla::NumberIsInt32(d, &i)) { |
michael@0 | 1327 | if (base == 10 && StaticStrings::hasInt(i)) |
michael@0 | 1328 | return cx->staticStrings().getInt(i); |
michael@0 | 1329 | if (unsigned(i) < unsigned(base)) { |
michael@0 | 1330 | if (i < 10) |
michael@0 | 1331 | return cx->staticStrings().getInt(i); |
michael@0 | 1332 | jschar c = 'a' + i - 10; |
michael@0 | 1333 | JS_ASSERT(StaticStrings::hasUnit(c)); |
michael@0 | 1334 | return cx->staticStrings().getUnit(c); |
michael@0 | 1335 | } |
michael@0 | 1336 | |
michael@0 | 1337 | if (comp) { |
michael@0 | 1338 | if (JSFlatString *str = comp->dtoaCache.lookup(base, d)) |
michael@0 | 1339 | return str; |
michael@0 | 1340 | } |
michael@0 | 1341 | |
michael@0 | 1342 | size_t len; |
michael@0 | 1343 | numStr = Int32ToCString(&cbuf, i, &len, base); |
michael@0 | 1344 | JS_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); |
michael@0 | 1345 | } else { |
michael@0 | 1346 | if (comp) { |
michael@0 | 1347 | if (JSFlatString *str = comp->dtoaCache.lookup(base, d)) |
michael@0 | 1348 | return str; |
michael@0 | 1349 | } |
michael@0 | 1350 | |
michael@0 | 1351 | numStr = FracNumberToCString(cx, &cbuf, d, base); |
michael@0 | 1352 | if (!numStr) { |
michael@0 | 1353 | js_ReportOutOfMemory(cx); |
michael@0 | 1354 | return nullptr; |
michael@0 | 1355 | } |
michael@0 | 1356 | JS_ASSERT_IF(base == 10, |
michael@0 | 1357 | !cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); |
michael@0 | 1358 | JS_ASSERT_IF(base != 10, |
michael@0 | 1359 | cbuf.dbuf && cbuf.dbuf == numStr); |
michael@0 | 1360 | } |
michael@0 | 1361 | |
michael@0 | 1362 | JSFlatString *s = js_NewStringCopyZ<allowGC>(cx, numStr); |
michael@0 | 1363 | |
michael@0 | 1364 | if (comp) |
michael@0 | 1365 | comp->dtoaCache.cache(base, d, s); |
michael@0 | 1366 | |
michael@0 | 1367 | return s; |
michael@0 | 1368 | } |
michael@0 | 1369 | |
michael@0 | 1370 | template <AllowGC allowGC> |
michael@0 | 1371 | JSString * |
michael@0 | 1372 | js::NumberToString(ThreadSafeContext *cx, double d) |
michael@0 | 1373 | { |
michael@0 | 1374 | return js_NumberToStringWithBase<allowGC>(cx, d, 10); |
michael@0 | 1375 | } |
michael@0 | 1376 | |
michael@0 | 1377 | template JSString * |
michael@0 | 1378 | js::NumberToString<CanGC>(ThreadSafeContext *cx, double d); |
michael@0 | 1379 | |
michael@0 | 1380 | template JSString * |
michael@0 | 1381 | js::NumberToString<NoGC>(ThreadSafeContext *cx, double d); |
michael@0 | 1382 | |
michael@0 | 1383 | JSAtom * |
michael@0 | 1384 | js::NumberToAtom(ExclusiveContext *cx, double d) |
michael@0 | 1385 | { |
michael@0 | 1386 | int32_t si; |
michael@0 | 1387 | if (mozilla::NumberIsInt32(d, &si)) |
michael@0 | 1388 | return Int32ToAtom(cx, si); |
michael@0 | 1389 | |
michael@0 | 1390 | if (JSFlatString *str = LookupDtoaCache(cx, d)) |
michael@0 | 1391 | return AtomizeString(cx, str); |
michael@0 | 1392 | |
michael@0 | 1393 | ToCStringBuf cbuf; |
michael@0 | 1394 | char *numStr = FracNumberToCString(cx, &cbuf, d); |
michael@0 | 1395 | if (!numStr) { |
michael@0 | 1396 | js_ReportOutOfMemory(cx); |
michael@0 | 1397 | return nullptr; |
michael@0 | 1398 | } |
michael@0 | 1399 | JS_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); |
michael@0 | 1400 | |
michael@0 | 1401 | size_t length = strlen(numStr); |
michael@0 | 1402 | JSAtom *atom = Atomize(cx, numStr, length); |
michael@0 | 1403 | if (!atom) |
michael@0 | 1404 | return nullptr; |
michael@0 | 1405 | |
michael@0 | 1406 | CacheNumber(cx, d, atom); |
michael@0 | 1407 | |
michael@0 | 1408 | return atom; |
michael@0 | 1409 | } |
michael@0 | 1410 | |
michael@0 | 1411 | JSFlatString * |
michael@0 | 1412 | js::NumberToString(JSContext *cx, double d) |
michael@0 | 1413 | { |
michael@0 | 1414 | if (JSString *str = js_NumberToStringWithBase<CanGC>(cx, d, 10)) |
michael@0 | 1415 | return &str->asFlat(); |
michael@0 | 1416 | return nullptr; |
michael@0 | 1417 | } |
michael@0 | 1418 | |
michael@0 | 1419 | JSFlatString * |
michael@0 | 1420 | js::IndexToString(JSContext *cx, uint32_t index) |
michael@0 | 1421 | { |
michael@0 | 1422 | if (StaticStrings::hasUint(index)) |
michael@0 | 1423 | return cx->staticStrings().getUint(index); |
michael@0 | 1424 | |
michael@0 | 1425 | JSCompartment *c = cx->compartment(); |
michael@0 | 1426 | if (JSFlatString *str = c->dtoaCache.lookup(10, index)) |
michael@0 | 1427 | return str; |
michael@0 | 1428 | |
michael@0 | 1429 | JSFatInlineString *str = js_NewGCFatInlineString<CanGC>(cx); |
michael@0 | 1430 | if (!str) |
michael@0 | 1431 | return nullptr; |
michael@0 | 1432 | |
michael@0 | 1433 | jschar buffer[JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1]; |
michael@0 | 1434 | RangedPtr<jschar> end(buffer + JSFatInlineString::MAX_FAT_INLINE_LENGTH, |
michael@0 | 1435 | buffer, JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1); |
michael@0 | 1436 | *end = '\0'; |
michael@0 | 1437 | RangedPtr<jschar> start = BackfillIndexInCharBuffer(index, end); |
michael@0 | 1438 | |
michael@0 | 1439 | jschar *dst = str->init(end - start); |
michael@0 | 1440 | PodCopy(dst, start.get(), end - start + 1); |
michael@0 | 1441 | |
michael@0 | 1442 | c->dtoaCache.cache(10, index, str); |
michael@0 | 1443 | return str; |
michael@0 | 1444 | } |
michael@0 | 1445 | |
michael@0 | 1446 | bool JS_FASTCALL |
michael@0 | 1447 | js::NumberValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb) |
michael@0 | 1448 | { |
michael@0 | 1449 | /* Convert to C-string. */ |
michael@0 | 1450 | ToCStringBuf cbuf; |
michael@0 | 1451 | const char *cstr; |
michael@0 | 1452 | size_t cstrlen; |
michael@0 | 1453 | if (v.isInt32()) { |
michael@0 | 1454 | cstr = Int32ToCString(&cbuf, v.toInt32(), &cstrlen); |
michael@0 | 1455 | JS_ASSERT(cstrlen == strlen(cstr)); |
michael@0 | 1456 | } else { |
michael@0 | 1457 | cstr = NumberToCString(cx, &cbuf, v.toDouble()); |
michael@0 | 1458 | if (!cstr) { |
michael@0 | 1459 | JS_ReportOutOfMemory(cx); |
michael@0 | 1460 | return false; |
michael@0 | 1461 | } |
michael@0 | 1462 | cstrlen = strlen(cstr); |
michael@0 | 1463 | } |
michael@0 | 1464 | |
michael@0 | 1465 | /* |
michael@0 | 1466 | * Inflate to jschar string. The input C-string characters are < 127, so |
michael@0 | 1467 | * even if jschars are UTF-8, all chars should map to one jschar. |
michael@0 | 1468 | */ |
michael@0 | 1469 | JS_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize); |
michael@0 | 1470 | return sb.appendInflated(cstr, cstrlen); |
michael@0 | 1471 | } |
michael@0 | 1472 | |
michael@0 | 1473 | static bool |
michael@0 | 1474 | CharsToNumber(ThreadSafeContext *cx, const jschar *chars, size_t length, double *result) |
michael@0 | 1475 | { |
michael@0 | 1476 | if (length == 1) { |
michael@0 | 1477 | jschar c = chars[0]; |
michael@0 | 1478 | if ('0' <= c && c <= '9') |
michael@0 | 1479 | *result = c - '0'; |
michael@0 | 1480 | else if (unicode::IsSpace(c)) |
michael@0 | 1481 | *result = 0.0; |
michael@0 | 1482 | else |
michael@0 | 1483 | *result = GenericNaN(); |
michael@0 | 1484 | return true; |
michael@0 | 1485 | } |
michael@0 | 1486 | |
michael@0 | 1487 | const jschar *end = chars + length; |
michael@0 | 1488 | const jschar *bp = SkipSpace(chars, end); |
michael@0 | 1489 | |
michael@0 | 1490 | /* ECMA doesn't allow signed hex numbers (bug 273467). */ |
michael@0 | 1491 | if (end - bp >= 2 && bp[0] == '0' && (bp[1] == 'x' || bp[1] == 'X')) { |
michael@0 | 1492 | /* |
michael@0 | 1493 | * It's probably a hex number. Accept if there's at least one hex |
michael@0 | 1494 | * digit after the 0x, and if no non-whitespace characters follow all |
michael@0 | 1495 | * the hex digits. |
michael@0 | 1496 | */ |
michael@0 | 1497 | const jschar *endptr; |
michael@0 | 1498 | double d; |
michael@0 | 1499 | if (!GetPrefixInteger(cx, bp + 2, end, 16, &endptr, &d) || |
michael@0 | 1500 | endptr == bp + 2 || |
michael@0 | 1501 | SkipSpace(endptr, end) != end) |
michael@0 | 1502 | { |
michael@0 | 1503 | *result = GenericNaN(); |
michael@0 | 1504 | } else { |
michael@0 | 1505 | *result = d; |
michael@0 | 1506 | } |
michael@0 | 1507 | return true; |
michael@0 | 1508 | } |
michael@0 | 1509 | |
michael@0 | 1510 | /* |
michael@0 | 1511 | * Note that ECMA doesn't treat a string beginning with a '0' as |
michael@0 | 1512 | * an octal number here. This works because all such numbers will |
michael@0 | 1513 | * be interpreted as decimal by js_strtod. Also, any hex numbers |
michael@0 | 1514 | * that have made it here (which can only be negative ones) will |
michael@0 | 1515 | * be treated as 0 without consuming the 'x' by js_strtod. |
michael@0 | 1516 | */ |
michael@0 | 1517 | const jschar *ep; |
michael@0 | 1518 | double d; |
michael@0 | 1519 | if (!js_strtod(cx, bp, end, &ep, &d)) { |
michael@0 | 1520 | *result = GenericNaN(); |
michael@0 | 1521 | return false; |
michael@0 | 1522 | } |
michael@0 | 1523 | |
michael@0 | 1524 | if (SkipSpace(ep, end) != end) |
michael@0 | 1525 | *result = GenericNaN(); |
michael@0 | 1526 | else |
michael@0 | 1527 | *result = d; |
michael@0 | 1528 | |
michael@0 | 1529 | return true; |
michael@0 | 1530 | } |
michael@0 | 1531 | |
michael@0 | 1532 | bool |
michael@0 | 1533 | js::StringToNumber(ThreadSafeContext *cx, JSString *str, double *result) |
michael@0 | 1534 | { |
michael@0 | 1535 | ScopedThreadSafeStringInspector inspector(str); |
michael@0 | 1536 | if (!inspector.ensureChars(cx)) |
michael@0 | 1537 | return false; |
michael@0 | 1538 | |
michael@0 | 1539 | return CharsToNumber(cx, inspector.chars(), str->length(), result); |
michael@0 | 1540 | } |
michael@0 | 1541 | |
michael@0 | 1542 | bool |
michael@0 | 1543 | js::NonObjectToNumberSlow(ThreadSafeContext *cx, Value v, double *out) |
michael@0 | 1544 | { |
michael@0 | 1545 | JS_ASSERT(!v.isNumber()); |
michael@0 | 1546 | JS_ASSERT(!v.isObject()); |
michael@0 | 1547 | |
michael@0 | 1548 | if (v.isString()) |
michael@0 | 1549 | return StringToNumber(cx, v.toString(), out); |
michael@0 | 1550 | if (v.isBoolean()) { |
michael@0 | 1551 | *out = v.toBoolean() ? 1.0 : 0.0; |
michael@0 | 1552 | return true; |
michael@0 | 1553 | } |
michael@0 | 1554 | if (v.isNull()) { |
michael@0 | 1555 | *out = 0.0; |
michael@0 | 1556 | return true; |
michael@0 | 1557 | } |
michael@0 | 1558 | |
michael@0 | 1559 | JS_ASSERT(v.isUndefined()); |
michael@0 | 1560 | *out = GenericNaN(); |
michael@0 | 1561 | return true; |
michael@0 | 1562 | } |
michael@0 | 1563 | |
michael@0 | 1564 | #if defined(_MSC_VER) |
michael@0 | 1565 | # pragma optimize("g", off) |
michael@0 | 1566 | #endif |
michael@0 | 1567 | |
michael@0 | 1568 | bool |
michael@0 | 1569 | js::ToNumberSlow(ExclusiveContext *cx, Value v, double *out) |
michael@0 | 1570 | { |
michael@0 | 1571 | JS_ASSERT(!v.isNumber()); |
michael@0 | 1572 | goto skip_int_double; |
michael@0 | 1573 | for (;;) { |
michael@0 | 1574 | if (v.isNumber()) { |
michael@0 | 1575 | *out = v.toNumber(); |
michael@0 | 1576 | return true; |
michael@0 | 1577 | } |
michael@0 | 1578 | |
michael@0 | 1579 | skip_int_double: |
michael@0 | 1580 | if (!v.isObject()) |
michael@0 | 1581 | return NonObjectToNumberSlow(cx, v, out); |
michael@0 | 1582 | |
michael@0 | 1583 | if (!cx->isJSContext()) |
michael@0 | 1584 | return false; |
michael@0 | 1585 | |
michael@0 | 1586 | RootedValue v2(cx, v); |
michael@0 | 1587 | if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v2)) |
michael@0 | 1588 | return false; |
michael@0 | 1589 | v = v2; |
michael@0 | 1590 | if (v.isObject()) |
michael@0 | 1591 | break; |
michael@0 | 1592 | } |
michael@0 | 1593 | |
michael@0 | 1594 | *out = GenericNaN(); |
michael@0 | 1595 | return true; |
michael@0 | 1596 | } |
michael@0 | 1597 | |
michael@0 | 1598 | JS_PUBLIC_API(bool) |
michael@0 | 1599 | js::ToNumberSlow(JSContext *cx, Value v, double *out) |
michael@0 | 1600 | { |
michael@0 | 1601 | return ToNumberSlow(static_cast<ExclusiveContext *>(cx), v, out); |
michael@0 | 1602 | } |
michael@0 | 1603 | |
michael@0 | 1604 | #if defined(_MSC_VER) |
michael@0 | 1605 | # pragma optimize("", on) |
michael@0 | 1606 | #endif |
michael@0 | 1607 | |
michael@0 | 1608 | /* |
michael@0 | 1609 | * Convert a value to an int64_t, according to the WebIDL rules for long long |
michael@0 | 1610 | * conversion. Return converted value in *out on success, false on failure. |
michael@0 | 1611 | */ |
michael@0 | 1612 | JS_PUBLIC_API(bool) |
michael@0 | 1613 | js::ToInt64Slow(JSContext *cx, const HandleValue v, int64_t *out) |
michael@0 | 1614 | { |
michael@0 | 1615 | JS_ASSERT(!v.isInt32()); |
michael@0 | 1616 | double d; |
michael@0 | 1617 | if (v.isDouble()) { |
michael@0 | 1618 | d = v.toDouble(); |
michael@0 | 1619 | } else { |
michael@0 | 1620 | if (!ToNumberSlow(cx, v, &d)) |
michael@0 | 1621 | return false; |
michael@0 | 1622 | } |
michael@0 | 1623 | *out = ToInt64(d); |
michael@0 | 1624 | return true; |
michael@0 | 1625 | } |
michael@0 | 1626 | |
michael@0 | 1627 | /* |
michael@0 | 1628 | * Convert a value to an uint64_t, according to the WebIDL rules for unsigned long long |
michael@0 | 1629 | * conversion. Return converted value in *out on success, false on failure. |
michael@0 | 1630 | */ |
michael@0 | 1631 | JS_PUBLIC_API(bool) |
michael@0 | 1632 | js::ToUint64Slow(JSContext *cx, const HandleValue v, uint64_t *out) |
michael@0 | 1633 | { |
michael@0 | 1634 | JS_ASSERT(!v.isInt32()); |
michael@0 | 1635 | double d; |
michael@0 | 1636 | if (v.isDouble()) { |
michael@0 | 1637 | d = v.toDouble(); |
michael@0 | 1638 | } else { |
michael@0 | 1639 | if (!ToNumberSlow(cx, v, &d)) |
michael@0 | 1640 | return false; |
michael@0 | 1641 | } |
michael@0 | 1642 | *out = ToUint64(d); |
michael@0 | 1643 | return true; |
michael@0 | 1644 | } |
michael@0 | 1645 | |
michael@0 | 1646 | template <typename ContextType, |
michael@0 | 1647 | bool (*ToNumberSlowFn)(ContextType *, Value, double *), |
michael@0 | 1648 | typename ValueType> |
michael@0 | 1649 | static bool |
michael@0 | 1650 | ToInt32SlowImpl(ContextType *cx, const ValueType v, int32_t *out) |
michael@0 | 1651 | { |
michael@0 | 1652 | JS_ASSERT(!v.isInt32()); |
michael@0 | 1653 | double d; |
michael@0 | 1654 | if (v.isDouble()) { |
michael@0 | 1655 | d = v.toDouble(); |
michael@0 | 1656 | } else { |
michael@0 | 1657 | if (!ToNumberSlowFn(cx, v, &d)) |
michael@0 | 1658 | return false; |
michael@0 | 1659 | } |
michael@0 | 1660 | *out = ToInt32(d); |
michael@0 | 1661 | return true; |
michael@0 | 1662 | } |
michael@0 | 1663 | |
michael@0 | 1664 | JS_PUBLIC_API(bool) |
michael@0 | 1665 | js::ToInt32Slow(JSContext *cx, const HandleValue v, int32_t *out) |
michael@0 | 1666 | { |
michael@0 | 1667 | return ToInt32SlowImpl<JSContext, ToNumberSlow>(cx, v, out); |
michael@0 | 1668 | } |
michael@0 | 1669 | |
michael@0 | 1670 | bool |
michael@0 | 1671 | js::NonObjectToInt32Slow(ThreadSafeContext *cx, const Value &v, int32_t *out) |
michael@0 | 1672 | { |
michael@0 | 1673 | return ToInt32SlowImpl<ThreadSafeContext, NonObjectToNumberSlow>(cx, v, out); |
michael@0 | 1674 | } |
michael@0 | 1675 | |
michael@0 | 1676 | template <typename ContextType, |
michael@0 | 1677 | bool (*ToNumberSlowFn)(ContextType *, Value, double *), |
michael@0 | 1678 | typename ValueType> |
michael@0 | 1679 | static bool |
michael@0 | 1680 | ToUint32SlowImpl(ContextType *cx, const ValueType v, uint32_t *out) |
michael@0 | 1681 | { |
michael@0 | 1682 | JS_ASSERT(!v.isInt32()); |
michael@0 | 1683 | double d; |
michael@0 | 1684 | if (v.isDouble()) { |
michael@0 | 1685 | d = v.toDouble(); |
michael@0 | 1686 | } else { |
michael@0 | 1687 | if (!ToNumberSlowFn(cx, v, &d)) |
michael@0 | 1688 | return false; |
michael@0 | 1689 | } |
michael@0 | 1690 | *out = ToUint32(d); |
michael@0 | 1691 | return true; |
michael@0 | 1692 | } |
michael@0 | 1693 | |
michael@0 | 1694 | JS_PUBLIC_API(bool) |
michael@0 | 1695 | js::ToUint32Slow(JSContext *cx, const HandleValue v, uint32_t *out) |
michael@0 | 1696 | { |
michael@0 | 1697 | return ToUint32SlowImpl<JSContext, ToNumberSlow>(cx, v, out); |
michael@0 | 1698 | } |
michael@0 | 1699 | |
michael@0 | 1700 | bool |
michael@0 | 1701 | js::NonObjectToUint32Slow(ThreadSafeContext *cx, const Value &v, uint32_t *out) |
michael@0 | 1702 | { |
michael@0 | 1703 | return ToUint32SlowImpl<ThreadSafeContext, NonObjectToNumberSlow>(cx, v, out); |
michael@0 | 1704 | } |
michael@0 | 1705 | |
michael@0 | 1706 | JS_PUBLIC_API(bool) |
michael@0 | 1707 | js::ToUint16Slow(JSContext *cx, const HandleValue v, uint16_t *out) |
michael@0 | 1708 | { |
michael@0 | 1709 | JS_ASSERT(!v.isInt32()); |
michael@0 | 1710 | double d; |
michael@0 | 1711 | if (v.isDouble()) { |
michael@0 | 1712 | d = v.toDouble(); |
michael@0 | 1713 | } else if (!ToNumberSlow(cx, v, &d)) { |
michael@0 | 1714 | return false; |
michael@0 | 1715 | } |
michael@0 | 1716 | |
michael@0 | 1717 | if (d == 0 || !mozilla::IsFinite(d)) { |
michael@0 | 1718 | *out = 0; |
michael@0 | 1719 | return true; |
michael@0 | 1720 | } |
michael@0 | 1721 | |
michael@0 | 1722 | uint16_t u = (uint16_t) d; |
michael@0 | 1723 | if ((double)u == d) { |
michael@0 | 1724 | *out = u; |
michael@0 | 1725 | return true; |
michael@0 | 1726 | } |
michael@0 | 1727 | |
michael@0 | 1728 | bool neg = (d < 0); |
michael@0 | 1729 | d = floor(neg ? -d : d); |
michael@0 | 1730 | d = neg ? -d : d; |
michael@0 | 1731 | unsigned m = JS_BIT(16); |
michael@0 | 1732 | d = fmod(d, (double) m); |
michael@0 | 1733 | if (d < 0) |
michael@0 | 1734 | d += m; |
michael@0 | 1735 | *out = (uint16_t) d; |
michael@0 | 1736 | return true; |
michael@0 | 1737 | } |
michael@0 | 1738 | |
michael@0 | 1739 | bool |
michael@0 | 1740 | js_strtod(ThreadSafeContext *cx, const jschar *s, const jschar *send, |
michael@0 | 1741 | const jschar **ep, double *dp) |
michael@0 | 1742 | { |
michael@0 | 1743 | size_t i; |
michael@0 | 1744 | char cbuf[32]; |
michael@0 | 1745 | char *cstr, *istr, *estr; |
michael@0 | 1746 | bool negative; |
michael@0 | 1747 | double d; |
michael@0 | 1748 | |
michael@0 | 1749 | const jschar *s1 = SkipSpace(s, send); |
michael@0 | 1750 | size_t length = send - s1; |
michael@0 | 1751 | |
michael@0 | 1752 | /* Use cbuf to avoid malloc */ |
michael@0 | 1753 | if (length >= sizeof cbuf) { |
michael@0 | 1754 | cstr = (char *) cx->malloc_(length + 1); |
michael@0 | 1755 | if (!cstr) |
michael@0 | 1756 | return false; |
michael@0 | 1757 | } else { |
michael@0 | 1758 | cstr = cbuf; |
michael@0 | 1759 | } |
michael@0 | 1760 | |
michael@0 | 1761 | for (i = 0; i != length; i++) { |
michael@0 | 1762 | if (s1[i] >> 8) |
michael@0 | 1763 | break; |
michael@0 | 1764 | cstr[i] = (char)s1[i]; |
michael@0 | 1765 | } |
michael@0 | 1766 | cstr[i] = 0; |
michael@0 | 1767 | |
michael@0 | 1768 | istr = cstr; |
michael@0 | 1769 | if ((negative = (*istr == '-')) != 0 || *istr == '+') |
michael@0 | 1770 | istr++; |
michael@0 | 1771 | if (*istr == 'I' && !strncmp(istr, "Infinity", 8)) { |
michael@0 | 1772 | d = negative ? NegativeInfinity<double>() : PositiveInfinity<double>(); |
michael@0 | 1773 | estr = istr + 8; |
michael@0 | 1774 | } else { |
michael@0 | 1775 | int err; |
michael@0 | 1776 | d = js_strtod_harder(cx->dtoaState(), cstr, &estr, &err); |
michael@0 | 1777 | } |
michael@0 | 1778 | |
michael@0 | 1779 | i = estr - cstr; |
michael@0 | 1780 | if (cstr != cbuf) |
michael@0 | 1781 | js_free(cstr); |
michael@0 | 1782 | *ep = i ? s1 + i : s; |
michael@0 | 1783 | *dp = d; |
michael@0 | 1784 | return true; |
michael@0 | 1785 | } |