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