// -*- c-basic-offset: 2 -*- /* * This file is part of the KDE libraries * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) * Copyright (C) 2003 Peter Kelly (pmk@post.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "value.h" #include "object.h" #include "types.h" #include "interpreter.h" #include "operations.h" #include "number_object.h" #include "error_object.h" #include "dtoa.h" #include "number_object.lut.h" #include <assert.h> #include <math.h> using namespace KJS; // ------------------------------ NumberInstanceImp ---------------------------- const ClassInfo NumberInstanceImp::info = {"Number", 0, 0, 0}; NumberInstanceImp::NumberInstanceImp(ObjectImp *proto) : ObjectImp(proto) { } // ------------------------------ NumberPrototypeImp --------------------------- // ECMA 15.7.4 NumberPrototypeImp::NumberPrototypeImp(ExecState *exec, ObjectPrototypeImp *objProto, FunctionPrototypeImp *funcProto) : NumberInstanceImp(objProto) { Value protect(this); setInternalValue(NumberImp::zero()); // The constructor will be added later, after NumberObjectImp has been constructed putDirect(toStringPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToString, 1,toStringPropertyName),DontEnum); putDirect(toLocaleStringPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToLocaleString, 0,toLocaleStringPropertyName),DontEnum); putDirect(valueOfPropertyName,new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ValueOf, 0,valueOfPropertyName),DontEnum); putDirect("toFixed", new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToFixed, 1,"toFixed"),DontEnum); putDirect("toExponential",new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToExponential, 1,"toExponential"),DontEnum); putDirect("toPrecision",new NumberProtoFuncImp(exec,funcProto,NumberProtoFuncImp::ToPrecision, 1,"toPrecision"),DontEnum); } // ------------------------------ NumberProtoFuncImp --------------------------- NumberProtoFuncImp::NumberProtoFuncImp(ExecState * /*exec*/, FunctionPrototypeImp *funcProto, int i, int len, const Identifier &_ident) : InternalFunctionImp(funcProto), id(i) { Value protect(this); putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum); ident = _ident; } bool NumberProtoFuncImp::implementsCall() const { return true; } static UString integer_part_noexp(double d) { int decimalPoint; int signDummy; char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &signDummy, NULL); int length = strlen(result); // sign for non-zero, negative numbers UString str = d < 0 ? "-" : ""; if (decimalPoint == 9999) { str += UString(result); } else if (decimalPoint <= 0) { str += UString("0"); } else { char *buf; if (length <= decimalPoint) { buf = (char*)malloc(decimalPoint+1); strcpy(buf,result); memset(buf+length,'0',decimalPoint-length); } else { buf = (char*)malloc(decimalPoint+1); strncpy(buf,result,decimalPoint); } buf[decimalPoint] = '\0'; str += UString(buf); free(buf); } kjs_freedtoa(result); return str; } static UString char_sequence(char c, int count) { char *buf = (char*)malloc(count+1); memset(buf,c,count); buf[count] = '\0'; UString s(buf); free(buf); return s; } // ECMA 15.7.4.2 - 15.7.4.7 Value NumberProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args) { Value result; // no generic function. "this" has to be a Number object KJS_CHECK_THIS( NumberInstanceImp, thisObj ); // execute "toString()" or "valueOf()", respectively Value v = thisObj.internalValue(); switch (id) { case ToString: { int radix = 10; if (!args.isEmpty() && args[0].type() != UndefinedType) radix = args[0].toInteger(exec); if (radix < 2 || radix > 36 || radix == 10) result = String(v.toString(exec)); else { const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; // INT_MAX results in 1024 characters left of the dot with radix 2 // give the same space on the right side. safety checks are in place // unless someone finds a precise rule. char s[2048 + 3]; double x = v.toNumber(exec); if (isNaN(x) || isInf(x)) return String(UString::from(x)); // apply algorithm on absolute value. add sign later. bool neg = false; if (x < 0.0) { neg = true; x = -x; } // convert integer portion double f = floor(x); double d = f; char *dot = s + sizeof(s) / 2; char *p = dot; *p = '\0'; do { *--p = digits[int(fmod(d, double(radix)))]; d /= radix; } while ((d <= -1.0 || d >= 1.0) && p > s); // any decimal fraction ? d = x - f; const double eps = 0.001; // TODO: guessed. base on radix ? if (d < -eps || d > eps) { *dot++ = '.'; do { d *= radix; *dot++ = digits[int(d)]; d -= int(d); } while ((d < -eps || d > eps) && dot - s < int(sizeof(s)) - 1); *dot = '\0'; } // add sign if negative if (neg) *--p = '-'; result = String(p); } break; } case ToLocaleString: /* TODO */ result = String(v.toString(exec)); break; case ValueOf: result = Number(v.toNumber(exec)); break; case ToFixed: { // FIXME: firefox works for all values, not just 0..20. This includes // NaN, infinity, undefined, etc. This is just a hack to pass our regression // suite. Value fractionDigits = args[0]; int f = -1; double fd = fractionDigits.toNumber(exec); if (isNaN(fd)) { f = 0; } else if (!isInf(fd)) { f = int(fd); } if (f < 0 || f > 20) { Object err = Error::create(exec,RangeError); exec->setException(err); return err; } double x = v.toNumber(exec); if (isNaN(x)) return String("NaN"); UString s = ""; if (x < 0) { s += "-"; x = -x; } if (x >= 1e21) return String(s+UString::from(x)); double n = floor(x*pow(10.0,f)); if (fabs(n/pow(10.0,f)-x) > fabs((n+1)/pow(10.0,f)-x)) n++; UString m = integer_part_noexp(n); int k = m.size(); if (k <= f) { UString z = ""; for (int i = 0; i < f+1-k; i++) z += "0"; m = z + m; k = f + 1; assert(k == m.size()); } if (k-f < m.size()) return String(s+m.substr(0,k-f)+"."+m.substr(k-f)); else return String(s+m.substr(0,k-f)); } case ToExponential: { double x = v.toNumber(exec); if (isNaN(x) || isInf(x)) return String(UString::from(x)); int f = 1; Value fractionDigits = args[0]; if (args.size() > 0) { f = fractionDigits.toInteger(exec); if (f < 0 || f > 20) { Object err = Error::create(exec,RangeError); exec->setException(err); return err; } } int decimalAdjust = 0; if (!fractionDigits.isA(UndefinedType)) { double logx = floor(log10(fabs(x))); x /= pow(10.0,logx); double fx = floor(x*pow(10.0,f))/pow(10.0,f); double cx = ceil(x*pow(10.0,f))/pow(10.0,f); if (fabs(fx-x) < fabs(cx-x)) x = fx; else x = cx; decimalAdjust = int(logx); } char buf[80]; int decimalPoint; int sign; if (isNaN(x)) return String("NaN"); char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, NULL); int length = strlen(result); decimalPoint += decimalAdjust; int i = 0; if (sign) { buf[i++] = '-'; } if (decimalPoint == 999) { strcpy(buf + i, result); } else { buf[i++] = result[0]; if (fractionDigits.isA(UndefinedType)) f = length-1; if (length > 1 && f > 0) { buf[i++] = '.'; int haveFDigits = length-1; if (f < haveFDigits) { strncpy(buf+i,result+1, f); i += f; } else { strcpy(buf+i,result+1); i += length-1; for (int j = 0; j < f-haveFDigits; j++) buf[i++] = '0'; } } buf[i++] = 'e'; buf[i++] = (decimalPoint >= 0) ? '+' : '-'; // decimalPoint can't be more than 3 digits decimal given the // nature of float representation int exponential = decimalPoint - 1; if (exponential < 0) { exponential = exponential * -1; } if (exponential >= 100) { buf[i++] = '0' + exponential / 100; } if (exponential >= 10) { buf[i++] = '0' + (exponential % 100) / 10; } buf[i++] = '0' + exponential % 10; buf[i++] = '\0'; } assert(i <= 80); kjs_freedtoa(result); return String(UString(buf)); } case ToPrecision: { int e = 0; UString m; int p = args[0].toInteger(exec); double x = v.toNumber(exec); if (args[0].isA(UndefinedType) || isNaN(x) || isInf(x)) return String(v.toString(exec)); UString s = ""; if (x < 0) { s = "-"; x = -x; } if (p < 1 || p > 21) { Object err = Error::create(exec, RangeError, "toPrecision() argument must be between 1 and 21"); exec->setException(err); return err; } if (x != 0) { // suggestions for a better algorithm welcome! e = int(log10(x)); double n = floor(x/pow(10.0,e-p+1)); if (n < pow(10.0,p-1)) { // first guess was not good e = e - 1; n = floor(x/pow(10.0,e-p+1)); if (n >= pow(10.0,p)) { // violated constraint. try something else. n = pow(10.0,p-1); e = int(log10(x/n)) + p - 1; } } if (fabs((n+1)*pow(10.0,e-p+1)-x) < fabs(n*pow(10.0,e-p+1)-x)) n++; assert(pow(10.0,p-1) <= n); assert(n < pow(10.0,p)); m = integer_part_noexp(n); if (e < -6 || e >= p) { if (m.size() > 1) m = m.substr(0,1)+"."+m.substr(1); if (e >= 0) return String(s+m+"e+"+UString::from(e)); else return String(s+m+"e-"+UString::from(-e)); } } else { m = char_sequence('0',p); e = 0; } if (e == p-1) { return String(s+m); } else if (e >= 0) { if (e+1 < m.size()) return String(s+m.substr(0,e+1)+"."+m.substr(e+1)); else return String(s+m.substr(0,e+1)); } else { return String(s+"0."+char_sequence('0',-(e+1))+m); } } } return result; } // ------------------------------ NumberObjectImp ------------------------------ const ClassInfo NumberObjectImp::info = {"Function", &InternalFunctionImp::info, &numberTable, 0}; /* Source for number_object.lut.h @begin numberTable 5 NaN NumberObjectImp::NaNValue DontEnum|DontDelete|ReadOnly NEGATIVE_INFINITY NumberObjectImp::NegInfinity DontEnum|DontDelete|ReadOnly POSITIVE_INFINITY NumberObjectImp::PosInfinity DontEnum|DontDelete|ReadOnly MAX_VALUE NumberObjectImp::MaxValue DontEnum|DontDelete|ReadOnly MIN_VALUE NumberObjectImp::MinValue DontEnum|DontDelete|ReadOnly @end */ NumberObjectImp::NumberObjectImp(ExecState * /*exec*/, FunctionPrototypeImp *funcProto, NumberPrototypeImp *numberProto) : InternalFunctionImp(funcProto) { Value protect(this); // Number.Prototype putDirect(prototypePropertyName, numberProto, DontEnum|DontDelete|ReadOnly); // no. of arguments for constructor putDirect(lengthPropertyName, NumberImp::one(), ReadOnly|DontDelete|DontEnum); } Value NumberObjectImp::get(ExecState *exec, const Identifier &propertyName) const { return lookupGetValue<NumberObjectImp, InternalFunctionImp>( exec, propertyName, &numberTable, this ); } Value NumberObjectImp::getValueProperty(ExecState *, int token) const { // ECMA 15.7.3 switch(token) { case NaNValue: return Number(NaN); case NegInfinity: return Number(-Inf); case PosInfinity: return Number(Inf); case MaxValue: return Number(1.7976931348623157E+308); case MinValue: return Number(5E-324); } return Null(); } bool NumberObjectImp::implementsConstruct() const { return true; } // ECMA 15.7.1 Object NumberObjectImp::construct(ExecState *exec, const List &args) { ObjectImp *proto = exec->lexicalInterpreter()->builtinNumberPrototype().imp(); Object obj(new NumberInstanceImp(proto)); Number n; if (args.isEmpty()) n = Number(0); else n = args[0].toNumber(exec); obj.setInternalValue(n); return obj; } bool NumberObjectImp::implementsCall() const { return true; } // ECMA 15.7.2 Value NumberObjectImp::call(ExecState *exec, Object &/*thisObj*/, const List &args) { if (args.isEmpty()) return Number(0); else return Number(args[0].toNumber(exec)); }