michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS number type and wrapper class. michael@0: */ michael@0: michael@0: #include "jsnum.h" michael@0: michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/PodOperations.h" michael@0: #include "mozilla/RangedPtr.h" michael@0: michael@0: #ifdef HAVE_LOCALECONV michael@0: #include michael@0: #endif michael@0: #include michael@0: #include michael@0: michael@0: #include "double-conversion.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsdtoa.h" michael@0: #include "jsobj.h" michael@0: #include "jsstr.h" michael@0: #include "jstypes.h" michael@0: michael@0: #include "vm/GlobalObject.h" michael@0: #include "vm/NumericConversions.h" michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: michael@0: #include "vm/NumberObject-inl.h" michael@0: #include "vm/String-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::Abs; michael@0: using mozilla::MinNumberValue; michael@0: using mozilla::NegativeInfinity; michael@0: using mozilla::PodCopy; michael@0: using mozilla::PositiveInfinity; michael@0: using mozilla::RangedPtr; michael@0: using JS::GenericNaN; michael@0: michael@0: /* michael@0: * If we're accumulating a decimal number and the number is >= 2^53, then the michael@0: * fast result from the loop in Get{Prefix,Decimal}Integer may be inaccurate. michael@0: * Call js_strtod_harder to get the correct answer. michael@0: */ michael@0: static bool michael@0: ComputeAccurateDecimalInteger(ThreadSafeContext *cx, michael@0: const jschar *start, const jschar *end, double *dp) michael@0: { michael@0: size_t length = end - start; michael@0: char *cstr = cx->pod_malloc(length + 1); michael@0: if (!cstr) michael@0: return false; michael@0: michael@0: for (size_t i = 0; i < length; i++) { michael@0: char c = char(start[i]); michael@0: JS_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')); michael@0: cstr[i] = c; michael@0: } michael@0: cstr[length] = 0; michael@0: michael@0: char *estr; michael@0: int err = 0; michael@0: *dp = js_strtod_harder(cx->dtoaState(), cstr, &estr, &err); michael@0: if (err == JS_DTOA_ENOMEM) { michael@0: js_ReportOutOfMemory(cx); michael@0: js_free(cstr); michael@0: return false; michael@0: } michael@0: js_free(cstr); michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class BinaryDigitReader michael@0: { michael@0: const int base; /* Base of number; must be a power of 2 */ michael@0: int digit; /* Current digit value in radix given by base */ michael@0: int digitMask; /* Mask to extract the next bit from digit */ michael@0: const jschar *start; /* Pointer to the remaining digits */ michael@0: const jschar *end; /* Pointer to first non-digit */ michael@0: michael@0: public: michael@0: BinaryDigitReader(int base, const jschar *start, const jschar *end) michael@0: : base(base), digit(0), digitMask(0), start(start), end(end) michael@0: { michael@0: } michael@0: michael@0: /* Return the next binary digit from the number, or -1 if done. */ michael@0: int nextDigit() { michael@0: if (digitMask == 0) { michael@0: if (start == end) michael@0: return -1; michael@0: michael@0: int c = *start++; michael@0: JS_ASSERT(('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')); michael@0: if ('0' <= c && c <= '9') michael@0: digit = c - '0'; michael@0: else if ('a' <= c && c <= 'z') michael@0: digit = c - 'a' + 10; michael@0: else michael@0: digit = c - 'A' + 10; michael@0: digitMask = base >> 1; michael@0: } michael@0: michael@0: int bit = (digit & digitMask) != 0; michael@0: digitMask >>= 1; michael@0: return bit; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: /* michael@0: * The fast result might also have been inaccurate for power-of-two bases. This michael@0: * happens if the addition in value * 2 + digit causes a round-down to an even michael@0: * least significant mantissa bit when the first dropped bit is a one. If any michael@0: * of the following digits in the number (which haven't been added in yet) are michael@0: * nonzero, then the correct action would have been to round up instead of michael@0: * down. An example occurs when reading the number 0x1000000000000081, which michael@0: * rounds to 0x1000000000000000 instead of 0x1000000000000100. michael@0: */ michael@0: static double michael@0: ComputeAccurateBinaryBaseInteger(const jschar *start, const jschar *end, int base) michael@0: { michael@0: BinaryDigitReader bdr(base, start, end); michael@0: michael@0: /* Skip leading zeroes. */ michael@0: int bit; michael@0: do { michael@0: bit = bdr.nextDigit(); michael@0: } while (bit == 0); michael@0: michael@0: JS_ASSERT(bit == 1); // guaranteed by Get{Prefix,Decimal}Integer michael@0: michael@0: /* Gather the 53 significant bits (including the leading 1). */ michael@0: double value = 1.0; michael@0: for (int j = 52; j > 0; j--) { michael@0: bit = bdr.nextDigit(); michael@0: if (bit < 0) michael@0: return value; michael@0: value = value * 2 + bit; michael@0: } michael@0: michael@0: /* bit2 is the 54th bit (the first dropped from the mantissa). */ michael@0: int bit2 = bdr.nextDigit(); michael@0: if (bit2 >= 0) { michael@0: double factor = 2.0; michael@0: int sticky = 0; /* sticky is 1 if any bit beyond the 54th is 1 */ michael@0: int bit3; michael@0: michael@0: while ((bit3 = bdr.nextDigit()) >= 0) { michael@0: sticky |= bit3; michael@0: factor *= 2; michael@0: } michael@0: value += bit2 & (bit | sticky); michael@0: value *= factor; michael@0: } michael@0: michael@0: return value; michael@0: } michael@0: michael@0: double michael@0: js::ParseDecimalNumber(const JS::TwoByteChars chars) michael@0: { michael@0: MOZ_ASSERT(chars.length() > 0); michael@0: uint64_t dec = 0; michael@0: RangedPtr s = chars.start(), end = chars.end(); michael@0: do { michael@0: jschar c = *s; michael@0: MOZ_ASSERT('0' <= c && c <= '9'); michael@0: uint8_t digit = c - '0'; michael@0: uint64_t next = dec * 10 + digit; michael@0: MOZ_ASSERT(next < DOUBLE_INTEGRAL_PRECISION_LIMIT, michael@0: "next value won't be an integrally-precise double"); michael@0: dec = next; michael@0: } while (++s < end); michael@0: return static_cast(dec); michael@0: } michael@0: michael@0: bool michael@0: js::GetPrefixInteger(ThreadSafeContext *cx, const jschar *start, const jschar *end, int base, michael@0: const jschar **endp, double *dp) michael@0: { michael@0: JS_ASSERT(start <= end); michael@0: JS_ASSERT(2 <= base && base <= 36); michael@0: michael@0: const jschar *s = start; michael@0: double d = 0.0; michael@0: for (; s < end; s++) { michael@0: int digit; michael@0: jschar c = *s; michael@0: if ('0' <= c && c <= '9') michael@0: digit = c - '0'; michael@0: else if ('a' <= c && c <= 'z') michael@0: digit = c - 'a' + 10; michael@0: else if ('A' <= c && c <= 'Z') michael@0: digit = c - 'A' + 10; michael@0: else michael@0: break; michael@0: if (digit >= base) michael@0: break; michael@0: d = d * base + digit; michael@0: } michael@0: michael@0: *endp = s; michael@0: *dp = d; michael@0: michael@0: /* If we haven't reached the limit of integer precision, we're done. */ michael@0: if (d < DOUBLE_INTEGRAL_PRECISION_LIMIT) michael@0: return true; michael@0: michael@0: /* michael@0: * Otherwise compute the correct integer from the prefix of valid digits michael@0: * if we're computing for base ten or a power of two. Don't worry about michael@0: * other bases; see 15.1.2.2 step 13. michael@0: */ michael@0: if (base == 10) michael@0: return ComputeAccurateDecimalInteger(cx, start, s, dp); michael@0: if ((base & (base - 1)) == 0) michael@0: *dp = ComputeAccurateBinaryBaseInteger(start, s, base); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::GetDecimalInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, double *dp) michael@0: { michael@0: JS_ASSERT(start <= end); michael@0: michael@0: const jschar *s = start; michael@0: double d = 0.0; michael@0: for (; s < end; s++) { michael@0: jschar c = *s; michael@0: JS_ASSERT('0' <= c && c <= '9'); michael@0: int digit = c - '0'; michael@0: d = d * 10 + digit; michael@0: } michael@0: michael@0: *dp = d; michael@0: michael@0: // If we haven't reached the limit of integer precision, we're done. michael@0: if (d < DOUBLE_INTEGRAL_PRECISION_LIMIT) michael@0: return true; michael@0: michael@0: // Otherwise compute the correct integer from the prefix of valid digits. michael@0: return ComputeAccurateDecimalInteger(cx, start, s, dp); michael@0: } michael@0: michael@0: static bool michael@0: num_isNaN(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(true); michael@0: return true; michael@0: } michael@0: michael@0: double x; michael@0: if (!ToNumber(cx, args[0], &x)) michael@0: return false; michael@0: michael@0: args.rval().setBoolean(mozilla::IsNaN(x)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: num_isFinite(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: michael@0: double x; michael@0: if (!ToNumber(cx, args[0], &x)) michael@0: return false; michael@0: michael@0: args.rval().setBoolean(mozilla::IsFinite(x)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: num_parseFloat(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: if (args.length() == 0) { michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: JSString *str = ToString(cx, args[0]); michael@0: if (!str) michael@0: return false; michael@0: const jschar *bp = str->getChars(cx); michael@0: if (!bp) michael@0: return false; michael@0: const jschar *end = bp + str->length(); michael@0: const jschar *ep; michael@0: double d; michael@0: if (!js_strtod(cx, bp, end, &ep, &d)) michael@0: return false; michael@0: if (ep == bp) { michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: args.rval().setDouble(d); michael@0: return true; michael@0: } michael@0: michael@0: /* ES5 15.1.2.2. */ michael@0: bool michael@0: js::num_parseInt(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Fast paths and exceptional cases. */ michael@0: if (args.length() == 0) { michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: michael@0: if (args.length() == 1 || michael@0: (args[1].isInt32() && (args[1].toInt32() == 0 || args[1].toInt32() == 10))) { michael@0: if (args[0].isInt32()) { michael@0: args.rval().set(args[0]); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Step 1 is |inputString = ToString(string)|. When string >= michael@0: * 1e21, ToString(string) is in the form "NeM". 'e' marks the end of michael@0: * the word, which would mean the result of parseInt(string) should be |N|. michael@0: * michael@0: * To preserve this behaviour, we can't use the fast-path when string > michael@0: * 1e21, or else the result would be |NeM|. michael@0: * michael@0: * The same goes for values smaller than 1.0e-6, because the string would be in michael@0: * the form of "Ne-M". michael@0: */ michael@0: if (args[0].isDouble()) { michael@0: double d = args[0].toDouble(); michael@0: if (1.0e-6 < d && d < 1.0e21) { michael@0: args.rval().setNumber(floor(d)); michael@0: return true; michael@0: } michael@0: if (-1.0e21 < d && d < -1.0e-6) { michael@0: args.rval().setNumber(-floor(-d)); michael@0: return true; michael@0: } michael@0: if (d == 0.0) { michael@0: args.rval().setInt32(0); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Step 1. */ michael@0: RootedString inputString(cx, ToString(cx, args[0])); michael@0: if (!inputString) michael@0: return false; michael@0: args[0].setString(inputString); michael@0: michael@0: /* Steps 6-9. */ michael@0: bool stripPrefix = true; michael@0: int32_t radix; michael@0: if (!args.hasDefined(1)) { michael@0: radix = 10; michael@0: } else { michael@0: if (!ToInt32(cx, args[1], &radix)) michael@0: return false; michael@0: if (radix == 0) { michael@0: radix = 10; michael@0: } else { michael@0: if (radix < 2 || radix > 36) { michael@0: args.rval().setNaN(); michael@0: return true; michael@0: } michael@0: if (radix != 16) michael@0: stripPrefix = false; michael@0: } michael@0: } michael@0: michael@0: /* Step 2. */ michael@0: const jschar *s; michael@0: const jschar *end; michael@0: { michael@0: const jschar *ws = inputString->getChars(cx); michael@0: if (!ws) michael@0: return false; michael@0: end = ws + inputString->length(); michael@0: s = SkipSpace(ws, end); michael@0: michael@0: MOZ_ASSERT(ws <= s); michael@0: MOZ_ASSERT(s <= end); michael@0: } michael@0: michael@0: /* Steps 3-4. */ michael@0: bool negative = (s != end && s[0] == '-'); michael@0: michael@0: /* Step 5. */ michael@0: if (s != end && (s[0] == '-' || s[0] == '+')) michael@0: s++; michael@0: michael@0: /* Step 10. */ michael@0: if (stripPrefix) { michael@0: if (end - s >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { michael@0: s += 2; michael@0: radix = 16; michael@0: } michael@0: } michael@0: michael@0: /* Steps 11-15. */ michael@0: const jschar *actualEnd; michael@0: double number; michael@0: if (!GetPrefixInteger(cx, s, end, radix, &actualEnd, &number)) michael@0: return false; michael@0: if (s == actualEnd) michael@0: args.rval().setNaN(); michael@0: else michael@0: args.rval().setNumber(negative ? -number : number); michael@0: return true; michael@0: } michael@0: michael@0: static const JSFunctionSpec number_functions[] = { michael@0: JS_FN(js_isNaN_str, num_isNaN, 1,0), michael@0: JS_FN(js_isFinite_str, num_isFinite, 1,0), michael@0: JS_FN(js_parseFloat_str, num_parseFloat, 1,0), michael@0: JS_FN(js_parseInt_str, num_parseInt, 2,0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: const Class NumberObject::class_ = { michael@0: js_Number_str, michael@0: JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number), michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub michael@0: }; michael@0: michael@0: static bool michael@0: Number(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: /* Sample JS_CALLEE before clobbering. */ michael@0: bool isConstructing = args.isConstructing(); michael@0: michael@0: if (args.length() > 0) { michael@0: if (!ToNumber(cx, args[0])) michael@0: return false; michael@0: args.rval().set(args[0]); michael@0: } else { michael@0: args.rval().setInt32(0); michael@0: } michael@0: michael@0: if (!isConstructing) michael@0: return true; michael@0: michael@0: JSObject *obj = NumberObject::create(cx, args.rval().toNumber()); michael@0: if (!obj) michael@0: return false; michael@0: args.rval().setObject(*obj); michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsNumber(HandleValue v) michael@0: { michael@0: return v.isNumber() || (v.isObject() && v.toObject().is()); michael@0: } michael@0: michael@0: static inline double michael@0: Extract(const Value &v) michael@0: { michael@0: if (v.isNumber()) michael@0: return v.toNumber(); michael@0: return v.toObject().as().unbox(); michael@0: } michael@0: michael@0: #if JS_HAS_TOSOURCE michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_toSource_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: double d = Extract(args.thisv()); michael@0: michael@0: StringBuffer sb(cx); michael@0: if (!sb.append("(new Number(") || michael@0: !NumberValueToStringBuffer(cx, NumberValue(d), sb) || michael@0: !sb.append("))")) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: JSString *str = sb.finishString(); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: num_toSource(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: #endif michael@0: michael@0: ToCStringBuf::ToCStringBuf() :dbuf(nullptr) michael@0: { michael@0: JS_STATIC_ASSERT(sbufSize >= DTOSTR_STANDARD_BUFFER_SIZE); michael@0: } michael@0: michael@0: ToCStringBuf::~ToCStringBuf() michael@0: { michael@0: js_free(dbuf); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: static JSFlatString * michael@0: LookupDtoaCache(ThreadSafeContext *cx, double d) michael@0: { michael@0: if (!cx->isExclusiveContext()) michael@0: return nullptr; michael@0: michael@0: if (JSCompartment *comp = cx->asExclusiveContext()->compartment()) { michael@0: if (JSFlatString *str = comp->dtoaCache.lookup(10, d)) michael@0: return str; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: static void michael@0: CacheNumber(ThreadSafeContext *cx, double d, JSFlatString *str) michael@0: { michael@0: if (!cx->isExclusiveContext()) michael@0: return; michael@0: michael@0: if (JSCompartment *comp = cx->asExclusiveContext()->compartment()) michael@0: comp->dtoaCache.cache(10, d, str); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE michael@0: static JSFlatString * michael@0: LookupInt32ToString(ThreadSafeContext *cx, int32_t si) michael@0: { michael@0: if (si >= 0 && StaticStrings::hasInt(si)) michael@0: return cx->staticStrings().getInt(si); michael@0: michael@0: return LookupDtoaCache(cx, si); michael@0: } michael@0: michael@0: template michael@0: MOZ_ALWAYS_INLINE michael@0: static T * michael@0: BackfillInt32InBuffer(int32_t si, T *buffer, size_t size, size_t *length) michael@0: { michael@0: uint32_t ui = Abs(si); michael@0: JS_ASSERT_IF(si == INT32_MIN, ui == uint32_t(INT32_MAX) + 1); michael@0: michael@0: RangedPtr end(buffer + size - 1, buffer, size); michael@0: *end = '\0'; michael@0: RangedPtr start = BackfillIndexInCharBuffer(ui, end); michael@0: if (si < 0) michael@0: *--start = '-'; michael@0: michael@0: *length = end - start; michael@0: return start.get(); michael@0: } michael@0: michael@0: template michael@0: JSFlatString * michael@0: js::Int32ToString(ThreadSafeContext *cx, int32_t si) michael@0: { michael@0: if (JSFlatString *str = LookupInt32ToString(cx, si)) michael@0: return str; michael@0: michael@0: JSFatInlineString *str = js_NewGCFatInlineString(cx); michael@0: if (!str) michael@0: return nullptr; michael@0: michael@0: jschar buffer[JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1]; michael@0: size_t length; michael@0: jschar *start = BackfillInt32InBuffer(si, buffer, michael@0: JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1, &length); michael@0: michael@0: PodCopy(str->init(length), start, length + 1); michael@0: michael@0: CacheNumber(cx, si, str); michael@0: return str; michael@0: } michael@0: michael@0: template JSFlatString * michael@0: js::Int32ToString(ThreadSafeContext *cx, int32_t si); michael@0: michael@0: template JSFlatString * michael@0: js::Int32ToString(ThreadSafeContext *cx, int32_t si); michael@0: michael@0: JSAtom * michael@0: js::Int32ToAtom(ExclusiveContext *cx, int32_t si) michael@0: { michael@0: if (JSFlatString *str = LookupInt32ToString(cx, si)) michael@0: return js::AtomizeString(cx, str); michael@0: michael@0: char buffer[JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1]; michael@0: size_t length; michael@0: char *start = BackfillInt32InBuffer(si, buffer, JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1, &length); michael@0: michael@0: JSAtom *atom = Atomize(cx, start, length); michael@0: if (!atom) michael@0: return nullptr; michael@0: michael@0: CacheNumber(cx, si, atom); michael@0: return atom; michael@0: } michael@0: michael@0: /* Returns a non-nullptr pointer to inside cbuf. */ michael@0: static char * michael@0: Int32ToCString(ToCStringBuf *cbuf, int32_t i, size_t *len, int base = 10) michael@0: { michael@0: uint32_t u = Abs(i); michael@0: michael@0: RangedPtr cp(cbuf->sbuf + ToCStringBuf::sbufSize - 1, cbuf->sbuf, ToCStringBuf::sbufSize); michael@0: char *end = cp.get(); michael@0: *cp = '\0'; michael@0: michael@0: /* Build the string from behind. */ michael@0: switch (base) { michael@0: case 10: michael@0: cp = BackfillIndexInCharBuffer(u, cp); michael@0: break; michael@0: case 16: michael@0: do { michael@0: unsigned newu = u / 16; michael@0: *--cp = "0123456789abcdef"[u - newu * 16]; michael@0: u = newu; michael@0: } while (u != 0); michael@0: break; michael@0: default: michael@0: JS_ASSERT(base >= 2 && base <= 36); michael@0: do { michael@0: unsigned newu = u / base; michael@0: *--cp = "0123456789abcdefghijklmnopqrstuvwxyz"[u - newu * base]; michael@0: u = newu; michael@0: } while (u != 0); michael@0: break; michael@0: } michael@0: if (i < 0) michael@0: *--cp = '-'; michael@0: michael@0: *len = end - cp.get(); michael@0: return cp.get(); michael@0: } michael@0: michael@0: template michael@0: static JSString * JS_FASTCALL michael@0: js_NumberToStringWithBase(ThreadSafeContext *cx, double d, int base); michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_toString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsNumber(args.thisv())); michael@0: michael@0: double d = Extract(args.thisv()); michael@0: michael@0: int32_t base = 10; michael@0: if (args.hasDefined(0)) { michael@0: double d2; michael@0: if (!ToInteger(cx, args[0], &d2)) michael@0: return false; michael@0: michael@0: if (d2 < 2 || d2 > 36) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_RADIX); michael@0: return false; michael@0: } michael@0: michael@0: base = int32_t(d2); michael@0: } michael@0: JSString *str = js_NumberToStringWithBase(cx, d, base); michael@0: if (!str) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_num_toString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: #if !EXPOSE_INTL_API michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_toLocaleString_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsNumber(args.thisv())); michael@0: michael@0: double d = Extract(args.thisv()); michael@0: michael@0: Rooted str(cx, js_NumberToStringWithBase(cx, d, 10)); michael@0: if (!str) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Create the string, move back to bytes to make string twiddling michael@0: * a bit easier and so we can insert platform charset seperators. michael@0: */ michael@0: JSAutoByteString numBytes(cx, str); michael@0: if (!numBytes) michael@0: return false; michael@0: const char *num = numBytes.ptr(); michael@0: if (!num) michael@0: return false; michael@0: michael@0: /* michael@0: * Find the first non-integer value, whether it be a letter as in michael@0: * 'Infinity', a decimal point, or an 'e' from exponential notation. michael@0: */ michael@0: const char *nint = num; michael@0: if (*nint == '-') michael@0: nint++; michael@0: while (*nint >= '0' && *nint <= '9') michael@0: nint++; michael@0: int digits = nint - num; michael@0: const char *end = num + digits; michael@0: if (!digits) { michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: JSRuntime *rt = cx->runtime(); michael@0: size_t thousandsLength = strlen(rt->thousandsSeparator); michael@0: size_t decimalLength = strlen(rt->decimalSeparator); michael@0: michael@0: /* Figure out how long resulting string will be. */ michael@0: int buflen = strlen(num); michael@0: if (*nint == '.') michael@0: buflen += decimalLength - 1; /* -1 to account for existing '.' */ michael@0: michael@0: const char *numGrouping; michael@0: const char *tmpGroup; michael@0: numGrouping = tmpGroup = rt->numGrouping; michael@0: int remainder = digits; michael@0: if (*num == '-') michael@0: remainder--; michael@0: michael@0: while (*tmpGroup != CHAR_MAX && *tmpGroup != '\0') { michael@0: if (*tmpGroup >= remainder) michael@0: break; michael@0: buflen += thousandsLength; michael@0: remainder -= *tmpGroup; michael@0: tmpGroup++; michael@0: } michael@0: michael@0: int nrepeat; michael@0: if (*tmpGroup == '\0' && *numGrouping != '\0') { michael@0: nrepeat = (remainder - 1) / tmpGroup[-1]; michael@0: buflen += thousandsLength * nrepeat; michael@0: remainder -= nrepeat * tmpGroup[-1]; michael@0: } else { michael@0: nrepeat = 0; michael@0: } michael@0: tmpGroup--; michael@0: michael@0: char *buf = cx->pod_malloc(buflen + 1); michael@0: if (!buf) michael@0: return false; michael@0: michael@0: char *tmpDest = buf; michael@0: const char *tmpSrc = num; michael@0: michael@0: while (*tmpSrc == '-' || remainder--) { michael@0: JS_ASSERT(tmpDest - buf < buflen); michael@0: *tmpDest++ = *tmpSrc++; michael@0: } michael@0: while (tmpSrc < end) { michael@0: JS_ASSERT(tmpDest - buf + ptrdiff_t(thousandsLength) <= buflen); michael@0: strcpy(tmpDest, rt->thousandsSeparator); michael@0: tmpDest += thousandsLength; michael@0: JS_ASSERT(tmpDest - buf + *tmpGroup <= buflen); michael@0: js_memcpy(tmpDest, tmpSrc, *tmpGroup); michael@0: tmpDest += *tmpGroup; michael@0: tmpSrc += *tmpGroup; michael@0: if (--nrepeat < 0) michael@0: tmpGroup--; michael@0: } michael@0: michael@0: if (*nint == '.') { michael@0: JS_ASSERT(tmpDest - buf + ptrdiff_t(decimalLength) <= buflen); michael@0: strcpy(tmpDest, rt->decimalSeparator); michael@0: tmpDest += decimalLength; michael@0: JS_ASSERT(tmpDest - buf + ptrdiff_t(strlen(nint + 1)) <= buflen); michael@0: strcpy(tmpDest, nint + 1); michael@0: } else { michael@0: JS_ASSERT(tmpDest - buf + ptrdiff_t(strlen(nint)) <= buflen); michael@0: strcpy(tmpDest, nint); michael@0: } michael@0: michael@0: if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode) { michael@0: Rooted v(cx, StringValue(str)); michael@0: bool ok = !!cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, &v); michael@0: if (ok) michael@0: args.rval().set(v); michael@0: js_free(buf); michael@0: return ok; michael@0: } michael@0: michael@0: str = js_NewStringCopyN(cx, buf, buflen); michael@0: js_free(buf); michael@0: if (!str) michael@0: return false; michael@0: michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: num_toLocaleString(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: #endif /* !EXPOSE_INTL_API */ michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_valueOf_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsNumber(args.thisv())); michael@0: args.rval().setNumber(Extract(args.thisv())); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_num_valueOf(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static const unsigned MAX_PRECISION = 100; michael@0: michael@0: static bool michael@0: ComputePrecisionInRange(JSContext *cx, int minPrecision, int maxPrecision, HandleValue v, michael@0: int *precision) michael@0: { michael@0: double prec; michael@0: if (!ToInteger(cx, v, &prec)) michael@0: return false; michael@0: if (minPrecision <= prec && prec <= maxPrecision) { michael@0: *precision = int(prec); michael@0: return true; michael@0: } michael@0: michael@0: ToCStringBuf cbuf; michael@0: if (char *numStr = NumberToCString(cx, &cbuf, prec, 10)) michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PRECISION_RANGE, numStr); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: DToStrResult(JSContext *cx, double d, JSDToStrMode mode, int precision, CallArgs args) michael@0: { michael@0: char buf[DTOSTR_VARIABLE_BUFFER_SIZE(MAX_PRECISION + 1)]; michael@0: char *numStr = js_dtostr(cx->mainThread().dtoaState, buf, sizeof buf, mode, precision, d); michael@0: if (!numStr) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: JSString *str = js_NewStringCopyZ(cx, numStr); michael@0: if (!str) michael@0: return false; michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * In the following three implementations, we allow a larger range of precision michael@0: * than ECMA requires; this is permitted by ECMA-262. michael@0: */ michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_toFixed_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsNumber(args.thisv())); michael@0: michael@0: int precision; michael@0: if (args.length() == 0) { michael@0: precision = 0; michael@0: } else { michael@0: if (!ComputePrecisionInRange(cx, -20, MAX_PRECISION, args[0], &precision)) michael@0: return false; michael@0: } michael@0: michael@0: return DToStrResult(cx, Extract(args.thisv()), DTOSTR_FIXED, precision, args); michael@0: } michael@0: michael@0: static bool michael@0: num_toFixed(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_toExponential_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsNumber(args.thisv())); michael@0: michael@0: JSDToStrMode mode; michael@0: int precision; michael@0: if (!args.hasDefined(0)) { michael@0: mode = DTOSTR_STANDARD_EXPONENTIAL; michael@0: precision = 0; michael@0: } else { michael@0: mode = DTOSTR_EXPONENTIAL; michael@0: if (!ComputePrecisionInRange(cx, 0, MAX_PRECISION, args[0], &precision)) michael@0: return false; michael@0: } michael@0: michael@0: return DToStrResult(cx, Extract(args.thisv()), mode, precision + 1, args); michael@0: } michael@0: michael@0: static bool michael@0: num_toExponential(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: num_toPrecision_impl(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(IsNumber(args.thisv())); michael@0: michael@0: double d = Extract(args.thisv()); michael@0: michael@0: if (!args.hasDefined(0)) { michael@0: JSString *str = js_NumberToStringWithBase(cx, d, 10); michael@0: if (!str) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: args.rval().setString(str); michael@0: return true; michael@0: } michael@0: michael@0: int precision; michael@0: if (!ComputePrecisionInRange(cx, 1, MAX_PRECISION, args[0], &precision)) michael@0: return false; michael@0: michael@0: return DToStrResult(cx, d, DTOSTR_PRECISION, precision, args); michael@0: } michael@0: michael@0: static bool michael@0: num_toPrecision(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: return CallNonGenericMethod(cx, args); michael@0: } michael@0: michael@0: static const JSFunctionSpec number_methods[] = { michael@0: #if JS_HAS_TOSOURCE michael@0: JS_FN(js_toSource_str, num_toSource, 0, 0), michael@0: #endif michael@0: JS_FN(js_toString_str, js_num_toString, 1, 0), michael@0: #if EXPOSE_INTL_API michael@0: JS_SELF_HOSTED_FN(js_toLocaleString_str, "Number_toLocaleString", 0,0), michael@0: #else michael@0: JS_FN(js_toLocaleString_str, num_toLocaleString, 0,0), michael@0: #endif michael@0: JS_FN(js_valueOf_str, js_num_valueOf, 0, 0), michael@0: JS_FN("toFixed", num_toFixed, 1, 0), michael@0: JS_FN("toExponential", num_toExponential, 1, 0), michael@0: JS_FN("toPrecision", num_toPrecision, 1, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: // ES6 draft ES6 15.7.3.10 michael@0: static bool michael@0: Number_isNaN(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1 || !args[0].isDouble()) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: args.rval().setBoolean(mozilla::IsNaN(args[0].toDouble())); michael@0: return true; michael@0: } michael@0: michael@0: // ES6 draft ES6 15.7.3.11 michael@0: static bool michael@0: Number_isFinite(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1 || !args[0].isNumber()) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: args.rval().setBoolean(args[0].isInt32() || michael@0: mozilla::IsFinite(args[0].toDouble())); michael@0: return true; michael@0: } michael@0: michael@0: // ES6 draft ES6 15.7.3.12 michael@0: static bool michael@0: Number_isInteger(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1 || !args[0].isNumber()) { michael@0: args.rval().setBoolean(false); michael@0: return true; michael@0: } michael@0: Value val = args[0]; michael@0: args.rval().setBoolean(val.isInt32() || michael@0: (mozilla::IsFinite(val.toDouble()) && michael@0: ToInteger(val.toDouble()) == val.toDouble())); michael@0: return true; michael@0: } michael@0: michael@0: // ES6 drafult ES6 15.7.3.13 michael@0: static bool michael@0: Number_toInteger(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: if (args.length() < 1) { michael@0: args.rval().setInt32(0); michael@0: return true; michael@0: } michael@0: double asint; michael@0: if (!ToInteger(cx, args[0], &asint)) michael@0: return false; michael@0: args.rval().setNumber(asint); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: static const JSFunctionSpec number_static_methods[] = { michael@0: JS_FN("isFinite", Number_isFinite, 1, 0), michael@0: JS_FN("isInteger", Number_isInteger, 1, 0), michael@0: JS_FN("isNaN", Number_isNaN, 1, 0), michael@0: JS_FN("toInteger", Number_toInteger, 1, 0), michael@0: /* ES6 additions. */ michael@0: JS_FN("parseFloat", num_parseFloat, 1, 0), michael@0: JS_FN("parseInt", num_parseInt, 2, 0), michael@0: JS_FS_END michael@0: }; michael@0: michael@0: michael@0: /* NB: Keep this in synch with number_constants[]. */ michael@0: enum nc_slot { michael@0: NC_NaN, michael@0: NC_POSITIVE_INFINITY, michael@0: NC_NEGATIVE_INFINITY, michael@0: NC_MAX_VALUE, michael@0: NC_MIN_VALUE, michael@0: NC_MAX_SAFE_INTEGER, michael@0: NC_MIN_SAFE_INTEGER, michael@0: NC_EPSILON, michael@0: NC_LIMIT michael@0: }; michael@0: michael@0: /* michael@0: * Some to most C compilers forbid spelling these at compile time, or barf michael@0: * if you try, so all but MAX_VALUE are set up by InitRuntimeNumberState michael@0: * using union jsdpun. michael@0: */ michael@0: static JSConstDoubleSpec number_constants[] = { michael@0: {0, "NaN", 0,{0,0,0}}, michael@0: {0, "POSITIVE_INFINITY", 0,{0,0,0}}, michael@0: {0, "NEGATIVE_INFINITY", 0,{0,0,0}}, michael@0: {1.7976931348623157E+308, "MAX_VALUE", 0,{0,0,0}}, michael@0: {0, "MIN_VALUE", 0,{0,0,0}}, michael@0: /* ES6 (April 2014 draft) 20.1.2.6 */ michael@0: {9007199254740991, "MAX_SAFE_INTEGER", 0,{0,0,0}}, michael@0: /* ES6 (April 2014 draft) 20.1.2.10 */ michael@0: {-9007199254740991, "MIN_SAFE_INTEGER", 0,{0,0,0}}, michael@0: /* ES6 (May 2013 draft) 15.7.3.7 */ michael@0: {2.2204460492503130808472633361816e-16, "EPSILON", 0,{0,0,0}}, michael@0: {0,0,0,{0,0,0}} michael@0: }; michael@0: michael@0: #if (defined __GNUC__ && defined __i386__) || \ michael@0: (defined __SUNPRO_CC && defined __i386) michael@0: michael@0: /* michael@0: * Set the exception mask to mask all exceptions and set the FPU precision michael@0: * to 53 bit mantissa (64 bit doubles). michael@0: */ michael@0: static inline void FIX_FPU() { michael@0: short control; michael@0: asm("fstcw %0" : "=m" (control) : ); michael@0: control &= ~0x300; // Lower bits 8 and 9 (precision control). michael@0: control |= 0x2f3; // Raise bits 0-5 (exception masks) and 9 (64-bit precision). michael@0: asm("fldcw %0" : : "m" (control) ); michael@0: } michael@0: michael@0: #else michael@0: michael@0: #define FIX_FPU() ((void)0) michael@0: michael@0: #endif michael@0: michael@0: bool michael@0: js::InitRuntimeNumberState(JSRuntime *rt) michael@0: { michael@0: FIX_FPU(); michael@0: michael@0: /* michael@0: * Our NaN must be one particular canonical value, because we rely on NaN michael@0: * encoding for our value representation. See Value.h. michael@0: */ michael@0: number_constants[NC_NaN].dval = GenericNaN(); michael@0: michael@0: number_constants[NC_POSITIVE_INFINITY].dval = mozilla::PositiveInfinity(); michael@0: number_constants[NC_NEGATIVE_INFINITY].dval = mozilla::NegativeInfinity(); michael@0: michael@0: number_constants[NC_MIN_VALUE].dval = MinNumberValue(); michael@0: michael@0: // XXX If EXPOSE_INTL_API becomes true all the time at some point, michael@0: // js::InitRuntimeNumberState is no longer fallible, and we should michael@0: // change its return type. michael@0: #if !EXPOSE_INTL_API michael@0: /* Copy locale-specific separators into the runtime strings. */ michael@0: const char *thousandsSeparator, *decimalPoint, *grouping; michael@0: #ifdef HAVE_LOCALECONV michael@0: struct lconv *locale = localeconv(); michael@0: thousandsSeparator = locale->thousands_sep; michael@0: decimalPoint = locale->decimal_point; michael@0: grouping = locale->grouping; michael@0: #else michael@0: thousandsSeparator = getenv("LOCALE_THOUSANDS_SEP"); michael@0: decimalPoint = getenv("LOCALE_DECIMAL_POINT"); michael@0: grouping = getenv("LOCALE_GROUPING"); michael@0: #endif michael@0: if (!thousandsSeparator) michael@0: thousandsSeparator = "'"; michael@0: if (!decimalPoint) michael@0: decimalPoint = "."; michael@0: if (!grouping) michael@0: grouping = "\3\0"; michael@0: michael@0: /* michael@0: * We use single malloc to get the memory for all separator and grouping michael@0: * strings. michael@0: */ michael@0: size_t thousandsSeparatorSize = strlen(thousandsSeparator) + 1; michael@0: size_t decimalPointSize = strlen(decimalPoint) + 1; michael@0: size_t groupingSize = strlen(grouping) + 1; michael@0: michael@0: char *storage = js_pod_malloc(thousandsSeparatorSize + michael@0: decimalPointSize + michael@0: groupingSize); michael@0: if (!storage) michael@0: return false; michael@0: michael@0: js_memcpy(storage, thousandsSeparator, thousandsSeparatorSize); michael@0: rt->thousandsSeparator = storage; michael@0: storage += thousandsSeparatorSize; michael@0: michael@0: js_memcpy(storage, decimalPoint, decimalPointSize); michael@0: rt->decimalSeparator = storage; michael@0: storage += decimalPointSize; michael@0: michael@0: js_memcpy(storage, grouping, groupingSize); michael@0: rt->numGrouping = grouping; michael@0: #endif /* !EXPOSE_INTL_API */ michael@0: return true; michael@0: } michael@0: michael@0: #if !EXPOSE_INTL_API michael@0: void michael@0: js::FinishRuntimeNumberState(JSRuntime *rt) michael@0: { michael@0: /* michael@0: * The free also releases the memory for decimalSeparator and numGrouping michael@0: * strings. michael@0: */ michael@0: char *storage = const_cast(rt->thousandsSeparator); michael@0: js_free(storage); michael@0: } michael@0: #endif michael@0: michael@0: JSObject * michael@0: js_InitNumberClass(JSContext *cx, HandleObject obj) michael@0: { michael@0: JS_ASSERT(obj->isNative()); michael@0: michael@0: /* XXX must do at least once per new thread, so do it per JSContext... */ michael@0: FIX_FPU(); michael@0: michael@0: Rooted global(cx, &obj->as()); michael@0: michael@0: RootedObject numberProto(cx, global->createBlankPrototype(cx, &NumberObject::class_)); michael@0: if (!numberProto) michael@0: return nullptr; michael@0: numberProto->as().setPrimitiveValue(0); michael@0: michael@0: RootedFunction ctor(cx); michael@0: ctor = global->createConstructor(cx, Number, cx->names().Number, 1); michael@0: if (!ctor) michael@0: return nullptr; michael@0: michael@0: if (!LinkConstructorAndPrototype(cx, ctor, numberProto)) michael@0: return nullptr; michael@0: michael@0: /* Add numeric constants (MAX_VALUE, NaN, &c.) to the Number constructor. */ michael@0: if (!JS_DefineConstDoubles(cx, ctor, number_constants)) michael@0: return nullptr; michael@0: michael@0: if (!DefinePropertiesAndBrand(cx, ctor, nullptr, number_static_methods)) michael@0: return nullptr; michael@0: michael@0: if (!DefinePropertiesAndBrand(cx, numberProto, nullptr, number_methods)) michael@0: return nullptr; michael@0: michael@0: if (!JS_DefineFunctions(cx, global, number_functions)) michael@0: return nullptr; michael@0: michael@0: RootedValue valueNaN(cx, cx->runtime()->NaNValue); michael@0: RootedValue valueInfinity(cx, cx->runtime()->positiveInfinityValue); michael@0: michael@0: /* ES5 15.1.1.1, 15.1.1.2 */ michael@0: if (!DefineNativeProperty(cx, global, cx->names().NaN, valueNaN, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: JSPROP_PERMANENT | JSPROP_READONLY) || michael@0: !DefineNativeProperty(cx, global, cx->names().Infinity, valueInfinity, michael@0: JS_PropertyStub, JS_StrictPropertyStub, michael@0: JSPROP_PERMANENT | JSPROP_READONLY)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Number, ctor, numberProto)) michael@0: return nullptr; michael@0: michael@0: return numberProto; michael@0: } michael@0: michael@0: static char * michael@0: FracNumberToCString(ThreadSafeContext *cx, ToCStringBuf *cbuf, double d, int base = 10) michael@0: { michael@0: #ifdef DEBUG michael@0: { michael@0: int32_t _; michael@0: JS_ASSERT(!mozilla::NumberIsInt32(d, &_)); michael@0: } michael@0: #endif michael@0: michael@0: char* numStr; michael@0: if (base == 10) { michael@0: /* michael@0: * This is V8's implementation of the algorithm described in the michael@0: * following paper: michael@0: * michael@0: * Printing floating-point numbers quickly and accurately with integers. michael@0: * Florian Loitsch, PLDI 2010. michael@0: */ michael@0: const double_conversion::DoubleToStringConverter &converter michael@0: = double_conversion::DoubleToStringConverter::EcmaScriptConverter(); michael@0: double_conversion::StringBuilder builder(cbuf->sbuf, cbuf->sbufSize); michael@0: converter.ToShortest(d, &builder); michael@0: numStr = builder.Finalize(); michael@0: } else { michael@0: numStr = cbuf->dbuf = js_dtobasestr(cx->dtoaState(), base, d); michael@0: } michael@0: return numStr; michael@0: } michael@0: michael@0: char * michael@0: js::NumberToCString(JSContext *cx, ToCStringBuf *cbuf, double d, int base/* = 10*/) michael@0: { michael@0: int32_t i; michael@0: size_t len; michael@0: return mozilla::NumberIsInt32(d, &i) michael@0: ? Int32ToCString(cbuf, i, &len, base) michael@0: : FracNumberToCString(cx, cbuf, d, base); michael@0: } michael@0: michael@0: template michael@0: static JSString * JS_FASTCALL michael@0: js_NumberToStringWithBase(ThreadSafeContext *cx, double d, int base) michael@0: { michael@0: ToCStringBuf cbuf; michael@0: char *numStr; michael@0: michael@0: /* michael@0: * Caller is responsible for error reporting. When called from trace, michael@0: * returning nullptr here will cause us to fall of trace and then retry michael@0: * from the interpreter (which will report the error). michael@0: */ michael@0: if (base < 2 || base > 36) michael@0: return nullptr; michael@0: michael@0: JSCompartment *comp = cx->isExclusiveContext() michael@0: ? cx->asExclusiveContext()->compartment() michael@0: : nullptr; michael@0: michael@0: int32_t i; michael@0: if (mozilla::NumberIsInt32(d, &i)) { michael@0: if (base == 10 && StaticStrings::hasInt(i)) michael@0: return cx->staticStrings().getInt(i); michael@0: if (unsigned(i) < unsigned(base)) { michael@0: if (i < 10) michael@0: return cx->staticStrings().getInt(i); michael@0: jschar c = 'a' + i - 10; michael@0: JS_ASSERT(StaticStrings::hasUnit(c)); michael@0: return cx->staticStrings().getUnit(c); michael@0: } michael@0: michael@0: if (comp) { michael@0: if (JSFlatString *str = comp->dtoaCache.lookup(base, d)) michael@0: return str; michael@0: } michael@0: michael@0: size_t len; michael@0: numStr = Int32ToCString(&cbuf, i, &len, base); michael@0: JS_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); michael@0: } else { michael@0: if (comp) { michael@0: if (JSFlatString *str = comp->dtoaCache.lookup(base, d)) michael@0: return str; michael@0: } michael@0: michael@0: numStr = FracNumberToCString(cx, &cbuf, d, base); michael@0: if (!numStr) { michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: JS_ASSERT_IF(base == 10, michael@0: !cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); michael@0: JS_ASSERT_IF(base != 10, michael@0: cbuf.dbuf && cbuf.dbuf == numStr); michael@0: } michael@0: michael@0: JSFlatString *s = js_NewStringCopyZ(cx, numStr); michael@0: michael@0: if (comp) michael@0: comp->dtoaCache.cache(base, d, s); michael@0: michael@0: return s; michael@0: } michael@0: michael@0: template michael@0: JSString * michael@0: js::NumberToString(ThreadSafeContext *cx, double d) michael@0: { michael@0: return js_NumberToStringWithBase(cx, d, 10); michael@0: } michael@0: michael@0: template JSString * michael@0: js::NumberToString(ThreadSafeContext *cx, double d); michael@0: michael@0: template JSString * michael@0: js::NumberToString(ThreadSafeContext *cx, double d); michael@0: michael@0: JSAtom * michael@0: js::NumberToAtom(ExclusiveContext *cx, double d) michael@0: { michael@0: int32_t si; michael@0: if (mozilla::NumberIsInt32(d, &si)) michael@0: return Int32ToAtom(cx, si); michael@0: michael@0: if (JSFlatString *str = LookupDtoaCache(cx, d)) michael@0: return AtomizeString(cx, str); michael@0: michael@0: ToCStringBuf cbuf; michael@0: char *numStr = FracNumberToCString(cx, &cbuf, d); michael@0: if (!numStr) { michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: JS_ASSERT(!cbuf.dbuf && numStr >= cbuf.sbuf && numStr < cbuf.sbuf + cbuf.sbufSize); michael@0: michael@0: size_t length = strlen(numStr); michael@0: JSAtom *atom = Atomize(cx, numStr, length); michael@0: if (!atom) michael@0: return nullptr; michael@0: michael@0: CacheNumber(cx, d, atom); michael@0: michael@0: return atom; michael@0: } michael@0: michael@0: JSFlatString * michael@0: js::NumberToString(JSContext *cx, double d) michael@0: { michael@0: if (JSString *str = js_NumberToStringWithBase(cx, d, 10)) michael@0: return &str->asFlat(); michael@0: return nullptr; michael@0: } michael@0: michael@0: JSFlatString * michael@0: js::IndexToString(JSContext *cx, uint32_t index) michael@0: { michael@0: if (StaticStrings::hasUint(index)) michael@0: return cx->staticStrings().getUint(index); michael@0: michael@0: JSCompartment *c = cx->compartment(); michael@0: if (JSFlatString *str = c->dtoaCache.lookup(10, index)) michael@0: return str; michael@0: michael@0: JSFatInlineString *str = js_NewGCFatInlineString(cx); michael@0: if (!str) michael@0: return nullptr; michael@0: michael@0: jschar buffer[JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1]; michael@0: RangedPtr end(buffer + JSFatInlineString::MAX_FAT_INLINE_LENGTH, michael@0: buffer, JSFatInlineString::MAX_FAT_INLINE_LENGTH + 1); michael@0: *end = '\0'; michael@0: RangedPtr start = BackfillIndexInCharBuffer(index, end); michael@0: michael@0: jschar *dst = str->init(end - start); michael@0: PodCopy(dst, start.get(), end - start + 1); michael@0: michael@0: c->dtoaCache.cache(10, index, str); michael@0: return str; michael@0: } michael@0: michael@0: bool JS_FASTCALL michael@0: js::NumberValueToStringBuffer(JSContext *cx, const Value &v, StringBuffer &sb) michael@0: { michael@0: /* Convert to C-string. */ michael@0: ToCStringBuf cbuf; michael@0: const char *cstr; michael@0: size_t cstrlen; michael@0: if (v.isInt32()) { michael@0: cstr = Int32ToCString(&cbuf, v.toInt32(), &cstrlen); michael@0: JS_ASSERT(cstrlen == strlen(cstr)); michael@0: } else { michael@0: cstr = NumberToCString(cx, &cbuf, v.toDouble()); michael@0: if (!cstr) { michael@0: JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: cstrlen = strlen(cstr); michael@0: } michael@0: michael@0: /* michael@0: * Inflate to jschar string. The input C-string characters are < 127, so michael@0: * even if jschars are UTF-8, all chars should map to one jschar. michael@0: */ michael@0: JS_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize); michael@0: return sb.appendInflated(cstr, cstrlen); michael@0: } michael@0: michael@0: static bool michael@0: CharsToNumber(ThreadSafeContext *cx, const jschar *chars, size_t length, double *result) michael@0: { michael@0: if (length == 1) { michael@0: jschar c = chars[0]; michael@0: if ('0' <= c && c <= '9') michael@0: *result = c - '0'; michael@0: else if (unicode::IsSpace(c)) michael@0: *result = 0.0; michael@0: else michael@0: *result = GenericNaN(); michael@0: return true; michael@0: } michael@0: michael@0: const jschar *end = chars + length; michael@0: const jschar *bp = SkipSpace(chars, end); michael@0: michael@0: /* ECMA doesn't allow signed hex numbers (bug 273467). */ michael@0: if (end - bp >= 2 && bp[0] == '0' && (bp[1] == 'x' || bp[1] == 'X')) { michael@0: /* michael@0: * It's probably a hex number. Accept if there's at least one hex michael@0: * digit after the 0x, and if no non-whitespace characters follow all michael@0: * the hex digits. michael@0: */ michael@0: const jschar *endptr; michael@0: double d; michael@0: if (!GetPrefixInteger(cx, bp + 2, end, 16, &endptr, &d) || michael@0: endptr == bp + 2 || michael@0: SkipSpace(endptr, end) != end) michael@0: { michael@0: *result = GenericNaN(); michael@0: } else { michael@0: *result = d; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Note that ECMA doesn't treat a string beginning with a '0' as michael@0: * an octal number here. This works because all such numbers will michael@0: * be interpreted as decimal by js_strtod. Also, any hex numbers michael@0: * that have made it here (which can only be negative ones) will michael@0: * be treated as 0 without consuming the 'x' by js_strtod. michael@0: */ michael@0: const jschar *ep; michael@0: double d; michael@0: if (!js_strtod(cx, bp, end, &ep, &d)) { michael@0: *result = GenericNaN(); michael@0: return false; michael@0: } michael@0: michael@0: if (SkipSpace(ep, end) != end) michael@0: *result = GenericNaN(); michael@0: else michael@0: *result = d; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::StringToNumber(ThreadSafeContext *cx, JSString *str, double *result) michael@0: { michael@0: ScopedThreadSafeStringInspector inspector(str); michael@0: if (!inspector.ensureChars(cx)) michael@0: return false; michael@0: michael@0: return CharsToNumber(cx, inspector.chars(), str->length(), result); michael@0: } michael@0: michael@0: bool michael@0: js::NonObjectToNumberSlow(ThreadSafeContext *cx, Value v, double *out) michael@0: { michael@0: JS_ASSERT(!v.isNumber()); michael@0: JS_ASSERT(!v.isObject()); michael@0: michael@0: if (v.isString()) michael@0: return StringToNumber(cx, v.toString(), out); michael@0: if (v.isBoolean()) { michael@0: *out = v.toBoolean() ? 1.0 : 0.0; michael@0: return true; michael@0: } michael@0: if (v.isNull()) { michael@0: *out = 0.0; michael@0: return true; michael@0: } michael@0: michael@0: JS_ASSERT(v.isUndefined()); michael@0: *out = GenericNaN(); michael@0: return true; michael@0: } michael@0: michael@0: #if defined(_MSC_VER) michael@0: # pragma optimize("g", off) michael@0: #endif michael@0: michael@0: bool michael@0: js::ToNumberSlow(ExclusiveContext *cx, Value v, double *out) michael@0: { michael@0: JS_ASSERT(!v.isNumber()); michael@0: goto skip_int_double; michael@0: for (;;) { michael@0: if (v.isNumber()) { michael@0: *out = v.toNumber(); michael@0: return true; michael@0: } michael@0: michael@0: skip_int_double: michael@0: if (!v.isObject()) michael@0: return NonObjectToNumberSlow(cx, v, out); michael@0: michael@0: if (!cx->isJSContext()) michael@0: return false; michael@0: michael@0: RootedValue v2(cx, v); michael@0: if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v2)) michael@0: return false; michael@0: v = v2; michael@0: if (v.isObject()) michael@0: break; michael@0: } michael@0: michael@0: *out = GenericNaN(); michael@0: return true; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: js::ToNumberSlow(JSContext *cx, Value v, double *out) michael@0: { michael@0: return ToNumberSlow(static_cast(cx), v, out); michael@0: } michael@0: michael@0: #if defined(_MSC_VER) michael@0: # pragma optimize("", on) michael@0: #endif michael@0: michael@0: /* michael@0: * Convert a value to an int64_t, according to the WebIDL rules for long long michael@0: * conversion. Return converted value in *out on success, false on failure. michael@0: */ michael@0: JS_PUBLIC_API(bool) michael@0: js::ToInt64Slow(JSContext *cx, const HandleValue v, int64_t *out) michael@0: { michael@0: JS_ASSERT(!v.isInt32()); michael@0: double d; michael@0: if (v.isDouble()) { michael@0: d = v.toDouble(); michael@0: } else { michael@0: if (!ToNumberSlow(cx, v, &d)) michael@0: return false; michael@0: } michael@0: *out = ToInt64(d); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Convert a value to an uint64_t, according to the WebIDL rules for unsigned long long michael@0: * conversion. Return converted value in *out on success, false on failure. michael@0: */ michael@0: JS_PUBLIC_API(bool) michael@0: js::ToUint64Slow(JSContext *cx, const HandleValue v, uint64_t *out) michael@0: { michael@0: JS_ASSERT(!v.isInt32()); michael@0: double d; michael@0: if (v.isDouble()) { michael@0: d = v.toDouble(); michael@0: } else { michael@0: if (!ToNumberSlow(cx, v, &d)) michael@0: return false; michael@0: } michael@0: *out = ToUint64(d); michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: ToInt32SlowImpl(ContextType *cx, const ValueType v, int32_t *out) michael@0: { michael@0: JS_ASSERT(!v.isInt32()); michael@0: double d; michael@0: if (v.isDouble()) { michael@0: d = v.toDouble(); michael@0: } else { michael@0: if (!ToNumberSlowFn(cx, v, &d)) michael@0: return false; michael@0: } michael@0: *out = ToInt32(d); michael@0: return true; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: js::ToInt32Slow(JSContext *cx, const HandleValue v, int32_t *out) michael@0: { michael@0: return ToInt32SlowImpl(cx, v, out); michael@0: } michael@0: michael@0: bool michael@0: js::NonObjectToInt32Slow(ThreadSafeContext *cx, const Value &v, int32_t *out) michael@0: { michael@0: return ToInt32SlowImpl(cx, v, out); michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: ToUint32SlowImpl(ContextType *cx, const ValueType v, uint32_t *out) michael@0: { michael@0: JS_ASSERT(!v.isInt32()); michael@0: double d; michael@0: if (v.isDouble()) { michael@0: d = v.toDouble(); michael@0: } else { michael@0: if (!ToNumberSlowFn(cx, v, &d)) michael@0: return false; michael@0: } michael@0: *out = ToUint32(d); michael@0: return true; michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: js::ToUint32Slow(JSContext *cx, const HandleValue v, uint32_t *out) michael@0: { michael@0: return ToUint32SlowImpl(cx, v, out); michael@0: } michael@0: michael@0: bool michael@0: js::NonObjectToUint32Slow(ThreadSafeContext *cx, const Value &v, uint32_t *out) michael@0: { michael@0: return ToUint32SlowImpl(cx, v, out); michael@0: } michael@0: michael@0: JS_PUBLIC_API(bool) michael@0: js::ToUint16Slow(JSContext *cx, const HandleValue v, uint16_t *out) michael@0: { michael@0: JS_ASSERT(!v.isInt32()); michael@0: double d; michael@0: if (v.isDouble()) { michael@0: d = v.toDouble(); michael@0: } else if (!ToNumberSlow(cx, v, &d)) { michael@0: return false; michael@0: } michael@0: michael@0: if (d == 0 || !mozilla::IsFinite(d)) { michael@0: *out = 0; michael@0: return true; michael@0: } michael@0: michael@0: uint16_t u = (uint16_t) d; michael@0: if ((double)u == d) { michael@0: *out = u; michael@0: return true; michael@0: } michael@0: michael@0: bool neg = (d < 0); michael@0: d = floor(neg ? -d : d); michael@0: d = neg ? -d : d; michael@0: unsigned m = JS_BIT(16); michael@0: d = fmod(d, (double) m); michael@0: if (d < 0) michael@0: d += m; michael@0: *out = (uint16_t) d; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js_strtod(ThreadSafeContext *cx, const jschar *s, const jschar *send, michael@0: const jschar **ep, double *dp) michael@0: { michael@0: size_t i; michael@0: char cbuf[32]; michael@0: char *cstr, *istr, *estr; michael@0: bool negative; michael@0: double d; michael@0: michael@0: const jschar *s1 = SkipSpace(s, send); michael@0: size_t length = send - s1; michael@0: michael@0: /* Use cbuf to avoid malloc */ michael@0: if (length >= sizeof cbuf) { michael@0: cstr = (char *) cx->malloc_(length + 1); michael@0: if (!cstr) michael@0: return false; michael@0: } else { michael@0: cstr = cbuf; michael@0: } michael@0: michael@0: for (i = 0; i != length; i++) { michael@0: if (s1[i] >> 8) michael@0: break; michael@0: cstr[i] = (char)s1[i]; michael@0: } michael@0: cstr[i] = 0; michael@0: michael@0: istr = cstr; michael@0: if ((negative = (*istr == '-')) != 0 || *istr == '+') michael@0: istr++; michael@0: if (*istr == 'I' && !strncmp(istr, "Infinity", 8)) { michael@0: d = negative ? NegativeInfinity() : PositiveInfinity(); michael@0: estr = istr + 8; michael@0: } else { michael@0: int err; michael@0: d = js_strtod_harder(cx->dtoaState(), cstr, &estr, &err); michael@0: } michael@0: michael@0: i = estr - cstr; michael@0: if (cstr != cbuf) michael@0: js_free(cstr); michael@0: *ep = i ? s1 + i : s; michael@0: *dp = d; michael@0: return true; michael@0: }