blob: 36f1dd1ff16da83abab42069d3deb43a1436f0e4 [file] [log] [blame]
// -*- c-basic-offset: 2 -*-
/*
* Copyright (C) 1999-2000,2003 Harri Porten (porten@kde.org)
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* 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 "config.h"
#include "number_object.h"
#include "number_object.lut.h"
#include "dtoa.h"
#include "error_object.h"
#include "operations.h"
#include <wtf/MathExtras.h>
#include <wtf/Vector.h>
using namespace KJS;
// ------------------------------ NumberInstance ----------------------------
const ClassInfo NumberInstance::info = {"Number", 0, 0, 0};
NumberInstance::NumberInstance(JSObject *proto)
: JSWrapperObject(proto)
{
}
// ------------------------------ NumberPrototype ---------------------------
// ECMA 15.7.4
NumberPrototype::NumberPrototype(ExecState *exec,
ObjectPrototype *objProto,
FunctionPrototype *funcProto)
: NumberInstance(objProto)
{
setInternalValue(jsNumber(0));
// The constructor will be added later, after NumberObjectImp has been constructed
putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToString, 1, exec->propertyNames().toString), DontEnum);
putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToLocaleString, 0, exec->propertyNames().toLocaleString), DontEnum);
putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ValueOf, 0, exec->propertyNames().valueOf), DontEnum);
putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToFixed, 1, exec->propertyNames().toFixed), DontEnum);
putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToExponential, 1, exec->propertyNames().toExponential), DontEnum);
putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToPrecision, 1, exec->propertyNames().toPrecision), DontEnum);
}
// ------------------------------ NumberProtoFunc ---------------------------
NumberProtoFunc::NumberProtoFunc(ExecState* exec, FunctionPrototype* funcProto, int i, int len, const Identifier& name)
: InternalFunctionImp(funcProto, name)
, id(i)
{
putDirect(exec->propertyNames().length, len, DontDelete|ReadOnly|DontEnum);
}
static UString integer_part_noexp(double d)
{
int decimalPoint;
int sign;
char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &sign, NULL);
size_t length = strlen(result);
UString str = sign ? "-" : "";
if (decimalPoint == 9999) {
str += UString(result);
} else if (decimalPoint <= 0) {
str += UString("0");
} else {
Vector<char, 1024> buf(decimalPoint + 1);
if (static_cast<int>(length) <= decimalPoint) {
strcpy(buf.data(), result);
memset(buf.data() + length, '0', decimalPoint - length);
} else
strncpy(buf.data(), result, decimalPoint);
buf[decimalPoint] = '\0';
str += UString(buf.data());
}
kjs_freedtoa(result);
return str;
}
static UString char_sequence(char c, int count)
{
Vector<char, 2048> buf(count + 1, c);
buf[count] = '\0';
return UString(buf.data());
}
static double intPow10(int e)
{
// This function uses the "exponentiation by squaring" algorithm and
// long double to quickly and precisely calculate integer powers of 10.0.
// This is a handy workaround for <rdar://problem/4494756>
if (e == 0)
return 1.0;
bool negative = e < 0;
unsigned exp = negative ? -e : e;
long double result = 10.0;
bool foundOne = false;
for (int bit = 31; bit >= 0; bit--) {
if (!foundOne) {
if ((exp >> bit) & 1)
foundOne = true;
} else {
result = result * result;
if ((exp >> bit) & 1)
result = result * 10.0;
}
}
if (negative)
return static_cast<double>(1.0 / result);
return static_cast<double>(result);
}
// ECMA 15.7.4.2 - 15.7.4.7
JSValue *NumberProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
{
// no generic function. "this" has to be a Number object
if (!thisObj->inherits(&NumberInstance::info))
return throwError(exec, TypeError);
JSValue *v = static_cast<NumberInstance*>(thisObj)->internalValue();
switch (id) {
case ToString: {
double dradix = 10;
if (!args.isEmpty())
dradix = args[0]->toInteger(exec);
if (dradix >= 2 && dradix <= 36 && dradix != 10) { // false for NaN
int radix = static_cast<int>(dradix);
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 jsString(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[static_cast<int>(fmod(d, 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[static_cast<int>(d)];
d -= static_cast<int>(d);
} while ((d < -eps || d > eps) && dot - s < static_cast<int>(sizeof(s)) - 1);
*dot = '\0';
}
// add sign if negative
if (neg)
*--p = '-';
return jsString(p);
} else
return jsString(v->toString(exec));
}
case ToLocaleString: /* TODO */
return jsString(v->toString(exec));
case ValueOf:
return jsNumber(v->toNumber(exec));
case ToFixed:
{
JSValue *fractionDigits = args[0];
double df = fractionDigits->toInteger(exec);
if (fractionDigits->isUndefined())
df = 0;
if (!(df >= 0 && df <= 20)) // true for NaN
return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20");
int f = (int)df;
double x = v->toNumber(exec);
if (isNaN(x))
return jsString("NaN");
UString s = "";
if (x < 0) {
s += "-";
x = -x;
}
if (x >= pow(10.0, 21.0))
return jsString(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 jsString(s+m.substr(0,k-f)+"."+m.substr(k-f));
else
return jsString(s+m.substr(0,k-f));
}
case ToExponential: {
double x = v->toNumber(exec);
if (isNaN(x) || isInf(x))
return jsString(UString::from(x));
JSValue *fractionDigits = args[0];
double df = fractionDigits->toInteger(exec);
if (!fractionDigits->isUndefined() && !(df >= 0 && df <= 20)) // true for NaN
return throwError(exec, RangeError, "toExponential() argument must between 0 and 20");
int f = (int)df;
int decimalAdjust = 0;
if (!fractionDigits->isUndefined()) {
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 = static_cast<int>(logx);
}
char buf[80];
int decimalPoint;
int sign;
if (isNaN(x))
return jsString("NaN");
char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, NULL);
size_t 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->isUndefined())
f = static_cast<int>(length) - 1;
if (length > 1 && f > 0) {
buf[i++] = '.';
int haveFDigits = static_cast<int>(length) - 1;
if (f < haveFDigits) {
strncpy(buf+i,result+1, f);
i += f;
}
else {
strcpy(buf+i,result+1);
i += static_cast<int>(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++] = static_cast<char>('0' + exponential / 100);
}
if (exponential >= 10) {
buf[i++] = static_cast<char>('0' + (exponential % 100) / 10);
}
buf[i++] = static_cast<char>('0' + exponential % 10);
buf[i++] = '\0';
}
assert(i <= 80);
kjs_freedtoa(result);
return jsString(buf);
}
case ToPrecision:
{
int e = 0;
UString m;
double dp = args[0]->toInteger(exec);
double x = v->toNumber(exec);
if (isNaN(dp) || isNaN(x) || isInf(x))
return jsString(v->toString(exec));
UString s = "";
if (x < 0) {
s = "-";
x = -x;
}
if (!(dp >= 1 && dp <= 21)) // true for NaN
return throwError(exec, RangeError, "toPrecision() argument must be between 1 and 21");
int p = (int)dp;
if (x != 0) {
e = static_cast<int>(log10(x));
double tens = intPow10(e - p + 1);
double n = floor(x / tens);
if (n < intPow10(p - 1)) {
e = e - 1;
tens = intPow10(e - p + 1);
n = floor(x / tens);
}
if (fabs((n + 1.0) * tens - x) <= fabs(n * tens - x))
++n;
assert(intPow10(p - 1) <= n);
assert(n < intPow10(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 jsString(s+m+"e+"+UString::from(e));
else
return jsString(s+m+"e-"+UString::from(-e));
}
}
else {
m = char_sequence('0',p);
e = 0;
}
if (e == p-1) {
return jsString(s+m);
}
else if (e >= 0) {
if (e+1 < m.size())
return jsString(s+m.substr(0,e+1)+"."+m.substr(e+1));
else
return jsString(s+m.substr(0,e+1));
}
else {
return jsString(s+"0."+char_sequence('0',-(e+1))+m);
}
}
}
return NULL;
}
// ------------------------------ 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, FunctionPrototype* funcProto, NumberPrototype* numberProto)
: InternalFunctionImp(funcProto)
{
// Number.Prototype
putDirect(exec->propertyNames().prototype, numberProto,DontEnum|DontDelete|ReadOnly);
// no. of arguments for constructor
putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly|DontDelete|DontEnum);
}
bool NumberObjectImp::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
{
return getStaticValueSlot<NumberObjectImp, InternalFunctionImp>(exec, &numberTable, this, propertyName, slot);
}
JSValue *NumberObjectImp::getValueProperty(ExecState *, int token) const
{
// ECMA 15.7.3
switch(token) {
case NaNValue:
return jsNaN();
case NegInfinity:
return jsNumber(-Inf);
case PosInfinity:
return jsNumber(Inf);
case MaxValue:
return jsNumber(1.7976931348623157E+308);
case MinValue:
return jsNumber(5E-324);
}
return jsNull();
}
bool NumberObjectImp::implementsConstruct() const
{
return true;
}
// ECMA 15.7.1
JSObject *NumberObjectImp::construct(ExecState *exec, const List &args)
{
JSObject *proto = exec->lexicalInterpreter()->builtinNumberPrototype();
NumberInstance *obj(new NumberInstance(proto));
double n;
if (args.isEmpty())
n = 0;
else
n = args[0]->toNumber(exec);
obj->setInternalValue(jsNumber(n));
return obj;
}
// ECMA 15.7.2
JSValue *NumberObjectImp::callAsFunction(ExecState *exec, JSObject* /*thisObj*/, const List &args)
{
if (args.isEmpty())
return jsNumber(0);
else
return jsNumber(args[0]->toNumber(exec));
}