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