2006-04-04 Eric Seidel <eseidel@apple.com>
[WebKit-https.git] / JavaScriptCore / kjs / number_object.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 1999-2000,2003 Harri Porten (porten@kde.org)
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #include "config.h"
23 #include "value.h"
24 #include "object.h"
25 #include "types.h"
26 #include "interpreter.h"
27 #include "operations.h"
28 #include "number_object.h"
29 #include "error_object.h"
30 #include "dtoa.h"
31
32 #include <kxmlcore/Vector.h>
33
34 #include "number_object.lut.h"
35
36 #include <assert.h>
37 #include <math.h>
38
39 using namespace KJS;
40
41
42 // ------------------------------ NumberInstance ----------------------------
43
44 const ClassInfo NumberInstance::info = {"Number", 0, 0, 0};
45
46 NumberInstance::NumberInstance(JSObject *proto)
47   : JSObject(proto)
48 {
49 }
50 // ------------------------------ NumberPrototype ---------------------------
51
52 // ECMA 15.7.4
53
54 NumberPrototype::NumberPrototype(ExecState *exec,
55                                        ObjectPrototype *objProto,
56                                        FunctionPrototype *funcProto)
57   : NumberInstance(objProto)
58 {
59   setInternalValue(jsNumber(0));
60
61   // The constructor will be added later, after NumberObjectImp has been constructed
62
63   putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToString,       1, toStringPropertyName), DontEnum);
64   putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToLocaleString, 0, toLocaleStringPropertyName), DontEnum);
65   putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ValueOf,        0, valueOfPropertyName), DontEnum);
66   putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToFixed,        1, toFixedPropertyName), DontEnum);
67   putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToExponential,  1, toExponentialPropertyName), DontEnum);
68   putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToPrecision,    1, toPrecisionPropertyName), DontEnum);
69 }
70
71
72 // ------------------------------ NumberProtoFunc ---------------------------
73
74 NumberProtoFunc::NumberProtoFunc(ExecState*, FunctionPrototype* funcProto, int i, int len, const Identifier& name)
75    : InternalFunctionImp(funcProto, name)
76    , id(i)
77 {
78   putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
79 }
80
81 static UString integer_part_noexp(double d)
82 {
83     int decimalPoint;
84     int sign;
85     char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &sign, NULL);
86     int length = strlen(result);
87     
88     UString str = sign ? "-" : "";
89     if (decimalPoint == 9999) {
90         str += UString(result);
91     } else if (decimalPoint <= 0) {
92         str += UString("0");
93     } else {
94         Vector<char, 1024> buf(decimalPoint + 1);
95         
96         if (length <= decimalPoint) {
97             strcpy(buf, result);
98             memset(buf + length, '0', decimalPoint - length);
99         } else
100             strncpy(buf, result, decimalPoint);
101         
102         buf[decimalPoint] = '\0';
103         str += UString(buf);
104     }
105     
106     kjs_freedtoa(result);
107     
108     return str;
109 }
110
111 static UString char_sequence(char c, int count)
112 {
113     Vector<char, 2048> buf(count + 1, c);
114     buf[count] = '\0';
115
116     return UString(buf);
117 }
118
119 static double intPow10(int e)
120 {
121   // This function uses the "exponentiation by squaring" algorithm and
122   // long double to quickly and precisely calculate integer powers of 10.0.
123
124   // This is a handy workaround for <rdar://problem/4494756>
125
126   if (e == 0)
127     return 1.0;
128
129   bool negative = e < 0;
130   unsigned exp = negative ? -e : e;
131
132   long double result = 10.0;
133   bool foundOne = false;
134   for (int bit = 31; bit >= 0; bit--) {
135     if (!foundOne) {
136       if ((exp >> bit) & 1)
137         foundOne = true;
138     } else {
139       result = result * result;
140       if ((exp >> bit) & 1)
141         result = result * 10.0;
142     }
143   }
144
145   if (negative)
146     return 1.0 / result;
147   else
148     return result;
149 }
150
151 // ECMA 15.7.4.2 - 15.7.4.7
152 JSValue *NumberProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
153 {
154   // no generic function. "this" has to be a Number object
155   if (!thisObj->inherits(&NumberInstance::info))
156     return throwError(exec, TypeError);
157
158   JSValue *v = thisObj->internalValue();
159   switch (id) {
160   case ToString: {
161     double dradix = 10;
162     if (!args.isEmpty())
163       dradix = args[0]->toInteger(exec);
164     if (dradix >= 2 && dradix <= 36 && dradix != 10) { // false for NaN
165       int radix = static_cast<int>(dradix);
166       const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
167       // INT_MAX results in 1024 characters left of the dot with radix 2
168       // give the same space on the right side. safety checks are in place
169       // unless someone finds a precise rule.
170       char s[2048 + 3];
171       double x = v->toNumber(exec);
172       if (isNaN(x) || isInf(x))
173         return jsString(UString::from(x));
174
175       // apply algorithm on absolute value. add sign later.
176       bool neg = false;
177       if (x < 0.0) {
178         neg = true;
179         x = -x;
180       }
181       // convert integer portion
182       double f = floor(x);
183       double d = f;
184       char *dot = s + sizeof(s) / 2;
185       char *p = dot;
186       *p = '\0';
187       do {
188         *--p = digits[static_cast<int>(fmod(d, radix))];
189         d /= radix;
190       } while ((d <= -1.0 || d >= 1.0) && p > s);
191       // any decimal fraction ?
192       d = x - f;
193       const double eps = 0.001; // TODO: guessed. base on radix ?
194       if (d < -eps || d > eps) {
195         *dot++ = '.';
196         do {
197           d *= radix;
198           *dot++ = digits[static_cast<int>(d)];
199           d -= static_cast<int>(d);
200         } while ((d < -eps || d > eps) && dot - s < static_cast<int>(sizeof(s)) - 1);
201         *dot = '\0';
202       }
203       // add sign if negative
204       if (neg)
205         *--p = '-';
206       return jsString(p);
207     } else
208       return jsString(v->toString(exec));
209   }
210   case ToLocaleString: /* TODO */
211     return jsString(v->toString(exec));
212   case ValueOf:
213     return jsNumber(v->toNumber(exec));
214   case ToFixed: 
215   {
216       JSValue *fractionDigits = args[0];
217       double df = fractionDigits->toInteger(exec);
218       if (fractionDigits->isUndefined())
219             df = 0;
220       if (!(df >= 0 && df <= 20)) // true for NaN
221           return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20");
222       int f = (int)df;
223       
224       double x = v->toNumber(exec);
225       if (isNaN(x))
226           return jsString("NaN");
227       
228       UString s = "";
229       if (x < 0) {
230           s += "-";
231           x = -x;
232       }
233       
234       if (x >= pow(10.0, 21.0))
235           return jsString(s+UString::from(x));
236       
237       double n = floor(x*pow(10.0, f));
238       if (fabs(n / pow(10.0, f) - x) >= fabs((n + 1) / pow(10.0, f) - x))
239           n++;
240       
241       UString m = integer_part_noexp(n);
242       
243       int k = m.size();
244       if (k <= f) {
245           UString z = "";
246           for (int i = 0; i < f+1-k; i++)
247               z += "0";
248           m = z + m;
249           k = f + 1;
250           assert(k == m.size());
251       }
252       if (k-f < m.size())
253           return jsString(s+m.substr(0,k-f)+"."+m.substr(k-f));
254       else
255           return jsString(s+m.substr(0,k-f));
256   }
257   case ToExponential: {
258       double x = v->toNumber(exec);
259       
260       if (isNaN(x) || isInf(x))
261           return jsString(UString::from(x));
262       
263       JSValue *fractionDigits = args[0];
264       double df = fractionDigits->toInteger(exec);
265       if (!fractionDigits->isUndefined() && !(df >= 0 && df <= 20)) // true for NaN
266           return throwError(exec, RangeError, "toExponential() argument must between 0 and 20");
267       int f = (int)df;
268       
269       int decimalAdjust = 0;
270       if (!fractionDigits->isUndefined()) {
271           double logx = floor(log10(x));
272           x /= pow(10.0, logx);
273           double fx = floor(x * pow(10.0, f)) / pow(10.0, f);
274           double cx = ceil(x * pow(10.0, f)) / pow(10.0, f);
275           
276           if (fabs(fx-x) < fabs(cx-x))
277               x = fx;
278           else
279               x = cx;
280           
281           decimalAdjust = static_cast<int>(logx);
282       }
283       
284       char buf[80];
285       int decimalPoint;
286       int sign;
287       
288       if (isNaN(x))
289           return jsString("NaN");
290       
291       char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, NULL);
292       int length = strlen(result);
293       decimalPoint += decimalAdjust;
294       
295       int i = 0;
296       if (sign) {
297           buf[i++] = '-';
298       }
299       
300       if (decimalPoint == 999) {
301           strcpy(buf + i, result);
302       } else {
303           buf[i++] = result[0];
304           
305           if (fractionDigits->isUndefined())
306               f = length-1;
307           
308           if (length > 1 && f > 0) {
309               buf[i++] = '.';
310               int haveFDigits = length-1;
311               if (f < haveFDigits) {
312                   strncpy(buf+i,result+1, f);
313                   i += f;
314               }
315               else {
316                   strcpy(buf+i,result+1);
317                   i += length-1;
318                   for (int j = 0; j < f-haveFDigits; j++)
319                       buf[i++] = '0';
320               }
321           }
322           
323           buf[i++] = 'e';
324           buf[i++] = (decimalPoint >= 0) ? '+' : '-';
325           // decimalPoint can't be more than 3 digits decimal given the
326           // nature of float representation
327           int exponential = decimalPoint - 1;
328           if (exponential < 0) {
329               exponential = exponential * -1;
330           }
331           if (exponential >= 100) {
332               buf[i++] = '0' + exponential / 100;
333           }
334           if (exponential >= 10) {
335               buf[i++] = '0' + (exponential % 100) / 10;
336           }
337           buf[i++] = '0' + exponential % 10;
338           buf[i++] = '\0';
339       }
340       
341       assert(i <= 80);
342       
343       kjs_freedtoa(result);
344       
345       return jsString(buf);
346   }
347   case ToPrecision:
348   {
349       int e = 0;
350       UString m;
351       
352       double dp = args[0]->toInteger(exec);
353       double x = v->toNumber(exec);
354       if (isNaN(dp) || isNaN(x) || isInf(x))
355           return jsString(v->toString(exec));
356       
357       UString s = "";
358       if (x < 0) {
359           s = "-";
360           x = -x;
361       }
362       
363       if (!(dp >= 1 && dp <= 21)) // true for NaN
364           return throwError(exec, RangeError, "toPrecision() argument must be between 1 and 21");
365       int p = (int)dp;
366       
367       if (x != 0) {
368           e = static_cast<int>(log10(x));
369           double tens = intPow10(e - p + 1);
370           double n = floor(x / tens);
371           if (n < intPow10(p - 1)) {
372               e = e - 1;
373               tens = intPow10(e - p + 1);
374               n = floor(x / tens);
375           }
376           
377           if (fabs((n + 1.0) * tens - x) <= fabs(n * tens - x))
378             ++n;
379           assert(intPow10(p - 1) <= n);
380           assert(n < intPow10(p));
381           
382           m = integer_part_noexp(n);
383           if (e < -6 || e >= p) {
384               if (m.size() > 1)
385                   m = m.substr(0,1)+"."+m.substr(1);
386               if (e >= 0)
387                   return jsString(s+m+"e+"+UString::from(e));
388               else
389                   return jsString(s+m+"e-"+UString::from(-e));
390           }
391       }
392       else {
393           m = char_sequence('0',p);
394           e = 0;
395       }
396       
397       if (e == p-1) {
398           return jsString(s+m);
399       }
400       else if (e >= 0) {
401           if (e+1 < m.size())
402               return jsString(s+m.substr(0,e+1)+"."+m.substr(e+1));
403           else
404               return jsString(s+m.substr(0,e+1));
405       }
406       else {
407           return jsString(s+"0."+char_sequence('0',-(e+1))+m);
408       }
409    }
410       
411  }
412   return NULL;
413 }
414
415 // ------------------------------ NumberObjectImp ------------------------------
416
417 const ClassInfo NumberObjectImp::info = {"Function", &InternalFunctionImp::info, &numberTable, 0};
418
419 /* Source for number_object.lut.h
420 @begin numberTable 5
421   NaN                   NumberObjectImp::NaNValue       DontEnum|DontDelete|ReadOnly
422   NEGATIVE_INFINITY     NumberObjectImp::NegInfinity    DontEnum|DontDelete|ReadOnly
423   POSITIVE_INFINITY     NumberObjectImp::PosInfinity    DontEnum|DontDelete|ReadOnly
424   MAX_VALUE             NumberObjectImp::MaxValue       DontEnum|DontDelete|ReadOnly
425   MIN_VALUE             NumberObjectImp::MinValue       DontEnum|DontDelete|ReadOnly
426 @end
427 */
428 NumberObjectImp::NumberObjectImp(ExecState*, FunctionPrototype* funcProto, NumberPrototype* numberProto)
429   : InternalFunctionImp(funcProto)
430 {
431   // Number.Prototype
432   putDirect(prototypePropertyName, numberProto,DontEnum|DontDelete|ReadOnly);
433
434   // no. of arguments for constructor
435   putDirect(lengthPropertyName, jsNumber(1), ReadOnly|DontDelete|DontEnum);
436 }
437
438 bool NumberObjectImp::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
439 {
440   return getStaticValueSlot<NumberObjectImp, InternalFunctionImp>(exec, &numberTable, this, propertyName, slot);
441 }
442
443 JSValue *NumberObjectImp::getValueProperty(ExecState *, int token) const
444 {
445   // ECMA 15.7.3
446   switch(token) {
447   case NaNValue:
448     return jsNaN();
449   case NegInfinity:
450     return jsNumber(-Inf);
451   case PosInfinity:
452     return jsNumber(Inf);
453   case MaxValue:
454     return jsNumber(1.7976931348623157E+308);
455   case MinValue:
456     return jsNumber(5E-324);
457   }
458   return jsNull();
459 }
460
461 bool NumberObjectImp::implementsConstruct() const
462 {
463   return true;
464 }
465
466
467 // ECMA 15.7.1
468 JSObject *NumberObjectImp::construct(ExecState *exec, const List &args)
469 {
470   JSObject *proto = exec->lexicalInterpreter()->builtinNumberPrototype();
471   JSObject *obj(new NumberInstance(proto));
472
473   double n;
474   if (args.isEmpty())
475     n = 0;
476   else
477     n = args[0]->toNumber(exec);
478
479   obj->setInternalValue(jsNumber(n));
480
481   return obj;
482 }
483
484 // ECMA 15.7.2
485 JSValue *NumberObjectImp::callAsFunction(ExecState *exec, JSObject */*thisObj*/, const List &args)
486 {
487   if (args.isEmpty())
488     return jsNumber(0);
489   else
490     return jsNumber(args[0]->toNumber(exec));
491 }