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