• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "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 
NumberPrototype(ExecState * exec,JSGlobalObject * globalObject,Structure * structure,Structure * functionStructure)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 
toThisNumber(JSValue thisValue,double & x)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 
getIntegerArgumentInRange(ExecState * exec,int low,int high,int & result,bool & isUndefined)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).
numberProtoFuncToExponential(ExecState * exec)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.
numberProtoFuncToFixed(ExecState * exec)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.
numberProtoFuncToPrecision(ExecState * exec)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 
numberProtoFuncToString(ExecState * exec)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 
numberProtoFuncToLocaleString(ExecState * exec)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 
numberProtoFuncValueOf(ExecState * exec)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