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
35namespace JSC {
36
37ASSERT_CLASS_FITS_IN_CELL(NumberPrototype);
38
39static EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState*);
40static EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState*);
41static EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState*);
42static EncodedJSValue JSC_HOST_CALL numberProtoFuncToFixed(ExecState*);
43static EncodedJSValue JSC_HOST_CALL numberProtoFuncToExponential(ExecState*);
44static EncodedJSValue JSC_HOST_CALL numberProtoFuncToPrecision(ExecState*);
45
46// ECMA 15.7.4
47
48NumberPrototype::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
67static 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
76static 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).
99EncodedJSValue 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.
129EncodedJSValue 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.
167EncodedJSValue 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
203EncodedJSValue 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
287EncodedJSValue 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
299EncodedJSValue 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