number_object.cpp
00001 // -*- c-basic-offset: 2 -*- 00002 /* 00003 * This file is part of the KDE libraries 00004 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) 00005 * Copyright (C) 2003 Peter Kelly (pmk@post.com) 00006 * 00007 * This library is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2 of the License, or (at your option) any later version. 00011 * 00012 * This library is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with this library; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 * 00021 */ 00022 00023 #include "value.h" 00024 #include "object.h" 00025 #include "types.h" 00026 #include "interpreter.h" 00027 #include "operations.h" 00028 #include "number_object.h" 00029 #include "error_object.h" 00030 #include "dtoa.h" 00031 00032 #include "number_object.lut.h" 00033 00034 #include <assert.h> 00035 #include <math.h> 00036 00037 using namespace KJS; 00038 00039 // ------------------------------ NumberInstanceImp ---------------------------- 00040 00041 const ClassInfo NumberInstanceImp::info = {"Number", 0, 0, 0}; 00042 00043 NumberInstanceImp::NumberInstanceImp(ObjectImp *proto) 00044 : ObjectImp(proto) 00045 { 00046 } 00047 // ------------------------------ NumberPrototypeImp --------------------------- 00048 00049 // ECMA 15.7.4 00050 00051 NumberPrototypeImp::NumberPrototypeImp(ExecState *exec, 00052 ObjectPrototypeImp *objProto, 00053 FunctionPrototypeImp *funcProto) 00054 : NumberInstanceImp(objProto) 00055 { 00056 Value protect(this); 00057 setInternalValue(NumberImp::zero()); 00058 00059 // The constructor will be added later, after NumberObjectImp has been constructed 00060 00061 putDirect(toStringPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToString, 00062 1,toStringPropertyName),DontEnum); 00063 putDirect(toLocaleStringPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToLocaleString, 00064 0,toLocaleStringPropertyName),DontEnum); 00065 putDirect(valueOfPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ValueOf, 00066 0,valueOfPropertyName),DontEnum); 00067 putDirect("toFixed", new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToFixed, 00068 1,"toFixed"),DontEnum); 00069 putDirect("toExponential",new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToExponential, 00070 1,"toExponential"),DontEnum); 00071 putDirect("toPrecision",new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToPrecision, 00072 1,"toPrecision"),DontEnum); 00073 } 00074 00075 00076 // ------------------------------ NumberProtoFuncImp --------------------------- 00077 00078 NumberProtoFuncImp::NumberProtoFuncImp(ExecState * /*exec*/, FunctionPrototypeImp *funcProto, 00079 int i, int len, const Identifier &_ident) 00080 : InternalFunctionImp(funcProto), id(i) 00081 { 00082 Value protect(this); 00083 putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum); 00084 ident = _ident; 00085 } 00086 00087 00088 bool NumberProtoFuncImp::implementsCall() const 00089 { 00090 return true; 00091 } 00092 00093 static UString integer_part_noexp(double d) 00094 { 00095 int decimalPoint; 00096 int signDummy; 00097 char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &signDummy, NULL); 00098 int length = strlen(result); 00099 00100 // sign for non-zero, negative numbers 00101 UString str = d < 0 ? "-" : ""; 00102 if (decimalPoint == 9999) { 00103 str += UString(result); 00104 } else if (decimalPoint <= 0) { 00105 str += UString("0"); 00106 } else { 00107 char *buf; 00108 00109 if (length <= decimalPoint) { 00110 buf = (char*)malloc(decimalPoint+1); 00111 strcpy(buf,result); 00112 memset(buf+length,'0',decimalPoint-length); 00113 } else { 00114 buf = (char*)malloc(decimalPoint+1); 00115 strncpy(buf,result,decimalPoint); 00116 } 00117 00118 buf[decimalPoint] = '\0'; 00119 str += UString(buf); 00120 free(buf); 00121 } 00122 00123 kjs_freedtoa(result); 00124 00125 return str; 00126 } 00127 00128 static UString char_sequence(char c, int count) 00129 { 00130 char *buf = (char*)malloc(count+1); 00131 memset(buf,c,count); 00132 buf[count] = '\0'; 00133 UString s(buf); 00134 free(buf); 00135 return s; 00136 } 00137 00138 // ECMA 15.7.4.2 - 15.7.4.7 00139 Value NumberProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args) 00140 { 00141 Value result; 00142 00143 // no generic function. "this" has to be a Number object 00144 KJS_CHECK_THIS( NumberInstanceImp, thisObj ); 00145 00146 // execute "toString()" or "valueOf()", respectively 00147 Value v = thisObj.internalValue(); 00148 switch (id) { 00149 case ToString: { 00150 int radix = 10; 00151 if (!args.isEmpty() && args[0].type() != UndefinedType) 00152 radix = args[0].toInteger(exec); 00153 if (radix < 2 || radix > 36 || radix == 10) 00154 result = String(v.toString(exec)); 00155 else { 00156 const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 00157 // INT_MAX results in 1024 characters left of the dot with radix 2 00158 // give the same space on the right side. safety checks are in place 00159 // unless someone finds a precise rule. 00160 char s[2048 + 3]; 00161 double x = v.toNumber(exec); 00162 if (isNaN(x) || isInf(x)) 00163 return String(UString::from(x)); 00164 // apply algorithm on absolute value. add sign later. 00165 bool neg = false; 00166 if (x < 0.0) { 00167 neg = true; 00168 x = -x; 00169 } 00170 // convert integer portion 00171 double f = floor(x); 00172 double d = f; 00173 char *dot = s + sizeof(s) / 2; 00174 char *p = dot; 00175 *p = '\0'; 00176 do { 00177 *--p = digits[int(fmod(d, double(radix)))]; 00178 d /= radix; 00179 } while ((d <= -1.0 || d >= 1.0) && p > s); 00180 // any decimal fraction ? 00181 d = x - f; 00182 const double eps = 0.001; // TODO: guessed. base on radix ? 00183 if (d < -eps || d > eps) { 00184 *dot++ = '.'; 00185 do { 00186 d *= radix; 00187 *dot++ = digits[int(d)]; 00188 d -= int(d); 00189 } while ((d < -eps || d > eps) && dot - s < int(sizeof(s)) - 1); 00190 *dot = '\0'; 00191 } 00192 // add sign if negative 00193 if (neg) 00194 *--p = '-'; 00195 result = String(p); 00196 } 00197 break; 00198 } 00199 case ToLocaleString: /* TODO */ 00200 result = String(v.toString(exec)); 00201 break; 00202 case ValueOf: 00203 result = Number(v.toNumber(exec)); 00204 break; 00205 case ToFixed: 00206 { 00207 // FIXME: firefox works for all values, not just 0..20. This includes 00208 // NaN, infinity, undefined, etc. This is just a hack to pass our regression 00209 // suite. 00210 Value fractionDigits = args[0]; 00211 int f = -1; 00212 double fd = fractionDigits.toNumber(exec); 00213 if (isNaN(fd)) { 00214 f = 0; 00215 } else if (!isInf(fd)) { 00216 f = int(fd); 00217 } 00218 if (f < 0 || f > 20) { 00219 Object err = Error::create(exec,RangeError); 00220 exec->setException(err); 00221 return err; 00222 } 00223 00224 double x = v.toNumber(exec); 00225 if (isNaN(x)) 00226 return String("NaN"); 00227 00228 UString s = ""; 00229 if (x < 0) { 00230 s += "-"; 00231 x = -x; 00232 } 00233 00234 if (x >= 1e21) 00235 return String(s+UString::from(x)); 00236 00237 double n = floor(x*pow(10.0,f)); 00238 if (fabs(n/pow(10.0,f)-x) > fabs((n+1)/pow(10.0,f)-x)) 00239 n++; 00240 00241 UString m = integer_part_noexp(n); 00242 00243 int k = m.size(); 00244 if (k <= f) { 00245 UString z = ""; 00246 for (int i = 0; i < f+1-k; i++) 00247 z += "0"; 00248 m = z + m; 00249 k = f + 1; 00250 assert(k == m.size()); 00251 } 00252 if (k-f < m.size()) 00253 return String(s+m.substr(0,k-f)+"."+m.substr(k-f)); 00254 else 00255 return String(s+m.substr(0,k-f)); 00256 } 00257 case ToExponential: { 00258 double x = v.toNumber(exec); 00259 00260 if (isNaN(x) || isInf(x)) 00261 return String(UString::from(x)); 00262 00263 int f = 1; 00264 Value fractionDigits = args[0]; 00265 if (args.size() > 0) { 00266 f = fractionDigits.toInteger(exec); 00267 if (f < 0 || f > 20) { 00268 Object err = Error::create(exec,RangeError); 00269 exec->setException(err); 00270 return err; 00271 } 00272 } 00273 00274 int decimalAdjust = 0; 00275 if (!fractionDigits.isA(UndefinedType)) { 00276 double logx = floor(log10(fabs(x))); 00277 x /= pow(10.0,logx); 00278 double fx = floor(x*pow(10.0,f))/pow(10.0,f); 00279 double cx = ceil(x*pow(10.0,f))/pow(10.0,f); 00280 00281 if (fabs(fx-x) < fabs(cx-x)) 00282 x = fx; 00283 else 00284 x = cx; 00285 00286 decimalAdjust = int(logx); 00287 } 00288 00289 char buf[80]; 00290 int decimalPoint; 00291 int sign; 00292 00293 if (isNaN(x)) 00294 return String("NaN"); 00295 00296 char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, NULL); 00297 int length = strlen(result); 00298 decimalPoint += decimalAdjust; 00299 00300 int i = 0; 00301 if (sign) { 00302 buf[i++] = '-'; 00303 } 00304 00305 if (decimalPoint == 999) { 00306 strcpy(buf + i, result); 00307 } else { 00308 buf[i++] = result[0]; 00309 00310 if (fractionDigits.isA(UndefinedType)) 00311 f = length-1; 00312 00313 if (length > 1 && f > 0) { 00314 buf[i++] = '.'; 00315 int haveFDigits = length-1; 00316 if (f < haveFDigits) { 00317 strncpy(buf+i,result+1, f); 00318 i += f; 00319 } 00320 else { 00321 strcpy(buf+i,result+1); 00322 i += length-1; 00323 for (int j = 0; j < f-haveFDigits; j++) 00324 buf[i++] = '0'; 00325 } 00326 } 00327 00328 buf[i++] = 'e'; 00329 buf[i++] = (decimalPoint >= 0) ? '+' : '-'; 00330 // decimalPoint can't be more than 3 digits decimal given the 00331 // nature of float representation 00332 int exponential = decimalPoint - 1; 00333 if (exponential < 0) { 00334 exponential = exponential * -1; 00335 } 00336 if (exponential >= 100) { 00337 buf[i++] = '0' + exponential / 100; 00338 } 00339 if (exponential >= 10) { 00340 buf[i++] = '0' + (exponential % 100) / 10; 00341 } 00342 buf[i++] = '0' + exponential % 10; 00343 buf[i++] = '\0'; 00344 } 00345 00346 assert(i <= 80); 00347 00348 kjs_freedtoa(result); 00349 00350 return String(UString(buf)); 00351 } 00352 case ToPrecision: 00353 { 00354 int e = 0; 00355 UString m; 00356 00357 int p = args[0].toInteger(exec); 00358 double x = v.toNumber(exec); 00359 if (args[0].isA(UndefinedType) || isNaN(x) || isInf(x)) 00360 return String(v.toString(exec)); 00361 00362 UString s = ""; 00363 if (x < 0) { 00364 s = "-"; 00365 x = -x; 00366 } 00367 00368 if (p < 1 || p > 21) { 00369 Object err = Error::create(exec, RangeError, 00370 "toPrecision() argument must be between 1 and 21"); 00371 exec->setException(err); 00372 return err; 00373 } 00374 00375 if (x != 0) { 00376 // suggestions for a better algorithm welcome! 00377 e = int(log10(x)); 00378 double n = floor(x/pow(10.0,e-p+1)); 00379 if (n < pow(10.0,p-1)) { 00380 // first guess was not good 00381 e = e - 1; 00382 n = floor(x/pow(10.0,e-p+1)); 00383 if (n >= pow(10.0,p)) { 00384 // violated constraint. try something else. 00385 n = pow(10.0,p-1); 00386 e = int(log10(x/n)) + p - 1; 00387 } 00388 } 00389 00390 if (fabs((n+1)*pow(10.0,e-p+1)-x) < fabs(n*pow(10.0,e-p+1)-x)) 00391 n++; 00392 assert(pow(10.0,p-1) <= n); 00393 assert(n < pow(10.0,p)); 00394 00395 m = integer_part_noexp(n); 00396 if (e < -6 || e >= p) { 00397 if (m.size() > 1) 00398 m = m.substr(0,1)+"."+m.substr(1); 00399 if (e >= 0) 00400 return String(s+m+"e+"+UString::from(e)); 00401 else 00402 return String(s+m+"e-"+UString::from(-e)); 00403 } 00404 } 00405 else { 00406 m = char_sequence('0',p); 00407 e = 0; 00408 } 00409 00410 if (e == p-1) { 00411 return String(s+m); 00412 } 00413 else if (e >= 0) { 00414 if (e+1 < m.size()) 00415 return String(s+m.substr(0,e+1)+"."+m.substr(e+1)); 00416 else 00417 return String(s+m.substr(0,e+1)); 00418 } 00419 else { 00420 return String(s+"0."+char_sequence('0',-(e+1))+m); 00421 } 00422 } 00423 } 00424 00425 return result; 00426 } 00427 00428 // ------------------------------ NumberObjectImp ------------------------------ 00429 00430 const ClassInfo NumberObjectImp::info = {"Function", &InternalFunctionImp::info, &numberTable, 0}; 00431 00432 /* Source for number_object.lut.h 00433 @begin numberTable 5 00434 NaN NumberObjectImp::NaNValue DontEnum|DontDelete|ReadOnly 00435 NEGATIVE_INFINITY NumberObjectImp::NegInfinity DontEnum|DontDelete|ReadOnly 00436 POSITIVE_INFINITY NumberObjectImp::PosInfinity DontEnum|DontDelete|ReadOnly 00437 MAX_VALUE NumberObjectImp::MaxValue DontEnum|DontDelete|ReadOnly 00438 MIN_VALUE NumberObjectImp::MinValue DontEnum|DontDelete|ReadOnly 00439 @end 00440 */ 00441 NumberObjectImp::NumberObjectImp(ExecState * /*exec*/, 00442 FunctionPrototypeImp *funcProto, 00443 NumberPrototypeImp *numberProto) 00444 : InternalFunctionImp(funcProto) 00445 { 00446 Value protect(this); 00447 // Number.Prototype 00448 putDirect(prototypePropertyName, numberProto, DontEnum|DontDelete|ReadOnly); 00449 00450 // no. of arguments for constructor 00451 putDirect(lengthPropertyName, NumberImp::one(), ReadOnly|DontDelete|DontEnum); 00452 } 00453 00454 Value NumberObjectImp::get(ExecState *exec, const Identifier &propertyName) const 00455 { 00456 return lookupGetValue<NumberObjectImp, InternalFunctionImp>( exec, propertyName, &numberTable, this ); 00457 } 00458 00459 Value NumberObjectImp::getValueProperty(ExecState *, int token) const 00460 { 00461 // ECMA 15.7.3 00462 switch(token) { 00463 case NaNValue: 00464 return Number(NaN); 00465 case NegInfinity: 00466 return Number(-Inf); 00467 case PosInfinity: 00468 return Number(Inf); 00469 case MaxValue: 00470 return Number(1.7976931348623157E+308); 00471 case MinValue: 00472 return Number(5E-324); 00473 } 00474 return Null(); 00475 } 00476 00477 bool NumberObjectImp::implementsConstruct() const 00478 { 00479 return true; 00480 } 00481 00482 00483 // ECMA 15.7.1 00484 Object NumberObjectImp::construct(ExecState *exec, const List &args) 00485 { 00486 ObjectImp *proto = exec->lexicalInterpreter()->builtinNumberPrototype().imp(); 00487 Object obj(new NumberInstanceImp(proto)); 00488 00489 Number n; 00490 if (args.isEmpty()) 00491 n = Number(0); 00492 else 00493 n = args[0].toNumber(exec); 00494 00495 obj.setInternalValue(n); 00496 00497 return obj; 00498 } 00499 00500 bool NumberObjectImp::implementsCall() const 00501 { 00502 return true; 00503 } 00504 00505 // ECMA 15.7.2 00506 Value NumberObjectImp::call(ExecState *exec, Object &/*thisObj*/, const List &args) 00507 { 00508 if (args.isEmpty()) 00509 return Number(0); 00510 else 00511 return Number(args[0].toNumber(exec)); 00512 }