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