Reviewed by Maciej.
- http://bugs.webkit.org/show_bug.cgi?id=15703
fix numeric functions -- improve correctness and speed
Gives about 1% gain on SunSpider.
* kjs/value.h: Added toIntegerPreserveNan, removed toUInt16.
(KJS::JSValue::toInt32): Changed to call getTruncatedInt32 in a way that works
with both immediate and number values.
(KJS::JSValue::toUInt32): Ditto.
* kjs/value.cpp:
(KJS::JSValue::toInteger): Moved the logic from roundValue here, with a couple
differences. One is that it now correctly returns 0 for NaN, and another is that
there's no special case for 0 or infinity, since the general case already handles
those correctly.
(KJS::JSValue::toIntegerPreserveNaN): Added. Like toInteger, but without the
check for NaN.
(KJS::JSValue::toInt32SlowCase): Call toNumber instead of roundValue. The
truncation done by the typecast already does the necessary truncation that
roundValue was doing.
(KJS::JSValue::toUInt32SlowCase): Ditto.
(KJS::JSValue::toUInt16): Removed.
* kjs/internal.h: Removed roundValue.
* kjs/internal.cpp: Ditto.
* kjs/array_object.cpp: (KJS::ArrayProtoFunc::callAsFunction): Remove unneeded
code to handle NaN in Array.slice; toInteger now never returns NaN as specified.
* kjs/date_object.cpp:
(KJS::fillStructuresUsingTimeArgs): Replaced call to roundValue with a call to
toNumber as specified.
(KJS::DateProtoFunc::callAsFunction): In SetTime case, replaced call to roundValue
with a call to toNumber and timeClip as specified.
(KJS::DateObjectImp::construct): Removed unnecessary checks of numArgs in cases
where the default behavior of toInt32 (returning 0) was already correct. Replaced
call to roundValue with a call to toNumber as specified.
(KJS::DateObjectFuncImp::callAsFunction): Ditto.
* kjs/math_object.cpp: (MathFuncImp::callAsFunction): Removed unnecessary special
cases for the pow function that the library already handles correctly.
* kjs/number_object.cpp: (NumberProtoFunc::callAsFunction): Changed ToString to
call toIntegerPreserveNaN, so we can continue to handle the NaN case differently.
The real toInteger now returns 0 for NaN. Took out unneeded special case in
ToFixed for undefined; was only needed because our toInteger was wrong. Same
thing in ToExponential. Changed ToPrecision to call toIntegerPreserveNaN.
* kjs/string_object.cpp:
(KJS::StringProtoFunc::callAsFunction): Took out CharAt and CharCodeAt special
cases for undefined that were only needed because toInteger was wrong. Same in
IndexOf, and was able to remove some special cases. In LastIndexOf, used
toIntegerPreserveNaN, but was able to remove some special cases there too.
Changed Substr implementation to preserve correct behavior with the change
to toInteger and match the specification. Also made sure we weren't converting
an out of range double to an int.
(KJS::StringObjectFuncImp::callAsFunction): Changed constructor to just use
toUInt32, because truncating toUInt32 to 16 bits is the same thing and there's
no reason to have toUInt16 as a second, less-optimized function that's only
called at this one call site.
* wtf/MathExtras.h: Added trunc function for Windows.
LayoutTests:
Reviewed by Maciej.
- test changes for http://bugs.webkit.org/show_bug.cgi?id=15703
fix numeric functions -- improve correctness and speed
* fast/js/resources/char-at.js: Updated test to expect that we get the first
character if we pass NaN to charAt and charCodeAt; it's what the specification
asks for and matches other browsers too.
* fast/js/char-at-expected.txt: Updated.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@27095
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2007-10-25 Darin Adler <darin@apple.com>
+
+ Reviewed by Maciej.
+
+ - http://bugs.webkit.org/show_bug.cgi?id=15703
+ fix numeric functions -- improve correctness and speed
+
+ Gives about 1% gain on SunSpider.
+
+ * kjs/value.h: Added toIntegerPreserveNan, removed toUInt16.
+ (KJS::JSValue::toInt32): Changed to call getTruncatedInt32 in a way that works
+ with both immediate and number values.
+ (KJS::JSValue::toUInt32): Ditto.
+ * kjs/value.cpp:
+ (KJS::JSValue::toInteger): Moved the logic from roundValue here, with a couple
+ differences. One is that it now correctly returns 0 for NaN, and another is that
+ there's no special case for 0 or infinity, since the general case already handles
+ those correctly.
+ (KJS::JSValue::toIntegerPreserveNaN): Added. Like toInteger, but without the
+ check for NaN.
+ (KJS::JSValue::toInt32SlowCase): Call toNumber instead of roundValue. The
+ truncation done by the typecast already does the necessary truncation that
+ roundValue was doing.
+ (KJS::JSValue::toUInt32SlowCase): Ditto.
+ (KJS::JSValue::toUInt16): Removed.
+
+ * kjs/internal.h: Removed roundValue.
+ * kjs/internal.cpp: Ditto.
+
+ * kjs/array_object.cpp: (KJS::ArrayProtoFunc::callAsFunction): Remove unneeded
+ code to handle NaN in Array.slice; toInteger now never returns NaN as specified.
+
+ * kjs/date_object.cpp:
+ (KJS::fillStructuresUsingTimeArgs): Replaced call to roundValue with a call to
+ toNumber as specified.
+ (KJS::DateProtoFunc::callAsFunction): In SetTime case, replaced call to roundValue
+ with a call to toNumber and timeClip as specified.
+ (KJS::DateObjectImp::construct): Removed unnecessary checks of numArgs in cases
+ where the default behavior of toInt32 (returning 0) was already correct. Replaced
+ call to roundValue with a call to toNumber as specified.
+ (KJS::DateObjectFuncImp::callAsFunction): Ditto.
+
+ * kjs/math_object.cpp: (MathFuncImp::callAsFunction): Removed unnecessary special
+ cases for the pow function that the library already handles correctly.
+
+ * kjs/number_object.cpp: (NumberProtoFunc::callAsFunction): Changed ToString to
+ call toIntegerPreserveNaN, so we can continue to handle the NaN case differently.
+ The real toInteger now returns 0 for NaN. Took out unneeded special case in
+ ToFixed for undefined; was only needed because our toInteger was wrong. Same
+ thing in ToExponential. Changed ToPrecision to call toIntegerPreserveNaN.
+
+ * kjs/string_object.cpp:
+ (KJS::StringProtoFunc::callAsFunction): Took out CharAt and CharCodeAt special
+ cases for undefined that were only needed because toInteger was wrong. Same in
+ IndexOf, and was able to remove some special cases. In LastIndexOf, used
+ toIntegerPreserveNaN, but was able to remove some special cases there too.
+ Changed Substr implementation to preserve correct behavior with the change
+ to toInteger and match the specification. Also made sure we weren't converting
+ an out of range double to an int.
+ (KJS::StringObjectFuncImp::callAsFunction): Changed constructor to just use
+ toUInt32, because truncating toUInt32 to 16 bits is the same thing and there's
+ no reason to have toUInt16 as a second, less-optimized function that's only
+ called at this one call site.
+
+ * wtf/MathExtras.h: Added trunc function for Windows.
+
2007-10-25 Geoffrey Garen <ggaren@apple.com>
Reviewed by Maciej Stachowiak.
// We return a new array
JSObject *resObj = static_cast<JSObject *>(exec->lexicalInterpreter()->builtinArray()->construct(exec,List::empty()));
result = resObj;
- double begin = 0;
- if (!args[0]->isUndefined()) {
- begin = args[0]->toInteger(exec);
- if (begin >= 0) { // false for NaN
- if (begin > length)
- begin = length;
- } else {
- begin += length;
- if (!(begin >= 0)) // true for NaN
- begin = 0;
- }
+ double begin = args[0]->toInteger(exec);
+ if (begin >= 0) {
+ if (begin > length)
+ begin = length;
+ } else {
+ begin += length;
+ if (begin < 0)
+ begin = 0;
}
- double end = length;
- if (!args[1]->isUndefined()) {
+ double end;
+ if (args[1]->isUndefined())
+ end = length;
+ else {
end = args[1]->toInteger(exec);
- if (end < 0) { // false for NaN
+ if (end < 0) {
end += length;
if (end < 0)
end = 0;
} else {
- if (!(end <= length)) // true for NaN
+ if (end > length)
end = length;
}
}
- //printf( "Slicing from %d to %d \n", begin, end );
int n = 0;
int b = static_cast<int>(begin);
int e = static_cast<int>(end);
// Documentation: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:lastIndexOf
int index = length - 1;
- double d = args[1]->toInteger(exec);
+ double d = args[1]->toIntegerPreserveNaN(exec);
if (d < 0) {
d += length;
// milliseconds
if (idx < numArgs) {
- milliseconds += roundValue(exec, args[idx]);
+ milliseconds += args[idx]->toNumber(exec);
} else {
milliseconds += *ms;
}
case GetTimezoneOffset:
return jsNumber(-gmtoffset(t) / minutesPerHour);
case SetTime:
- milli = roundValue(exec, args[0]);
+ milli = timeClip(args[0]->toNumber(exec));
result = jsNumber(milli);
thisDateObj->setInternalValue(result);
break;
t.year = (year >= 0 && year <= 99) ? year : year - 1900;
t.month = args[1]->toInt32(exec);
t.monthDay = (numArgs >= 3) ? args[2]->toInt32(exec) : 1;
- t.hour = (numArgs >= 4) ? args[3]->toInt32(exec) : 0;
- t.minute = (numArgs >= 5) ? args[4]->toInt32(exec) : 0;
- t.second = (numArgs >= 6) ? args[5]->toInt32(exec) : 0;
+ t.hour = args[3]->toInt32(exec);
+ t.minute = args[4]->toInt32(exec);
+ t.second = args[5]->toInt32(exec);
t.isDST = -1;
- double ms = (numArgs >= 7) ? roundValue(exec, args[6]) : 0;
+ double ms = (numArgs >= 7) ? args[6]->toNumber(exec) : 0;
value = gregorianDateTimeToMS(t, ms, false);
}
}
t.year = (year >= 0 && year <= 99) ? year : year - 1900;
t.month = args[1]->toInt32(exec);
t.monthDay = (n >= 3) ? args[2]->toInt32(exec) : 1;
- t.hour = (n >= 4) ? args[3]->toInt32(exec) : 0;
- t.minute = (n >= 5) ? args[4]->toInt32(exec) : 0;
- t.second = (n >= 6) ? args[5]->toInt32(exec) : 0;
- double ms = (n >= 7) ? roundValue(exec, args[6]) : 0;
+ t.hour = args[3]->toInt32(exec);
+ t.minute = args[4]->toInt32(exec);
+ t.second = args[5]->toInt32(exec);
+ double ms = (n >= 7) ? args[6]->toNumber(exec) : 0;
return jsNumber(gregorianDateTimeToMS(t, ms, true));
}
}
{
if (!isfinite(t))
return NaN;
- double at = fabs(t);
- if (at > 8.64E15)
+ if (fabs(t) > 8.64E15)
return NaN;
- return copysign(floor(at), t);
+ return trunc(t);
}
}
namespace KJS {
-#if PLATFORM(WIN_OS)
-#define copysign _copysign
-#endif
-
// ------------------------------ StringImp ------------------------------------
JSValue* StringImp::toPrimitive(ExecState*, JSType) const
// ------------------------------ global functions -----------------------------
-double roundValue(ExecState *exec, JSValue *v)
-{
- double d = v->toNumber(exec);
- double ad = fabs(d);
- if (ad == 0 || isNaN(d) || isInf(d))
- return d;
- return copysign(floor(ad), d);
-}
-
#ifndef NDEBUG
#include <stdio.h>
void printInfo(ExecState *exec, const char *s, JSValue *o, int lineno)
bool isAborted;
};
- // helper function for toInteger, toInt32, toUInt32 and toUInt16
- double roundValue(ExecState *, JSValue *);
-
#ifndef NDEBUG
void printInfo(ExecState *exec, const char *s, JSValue *, int lineno = -1);
#endif
// ECMA 15.8.2.1.13 (::pow takes care of most of the critera)
if (isNaN(arg2))
result = NaN;
- else if (isNaN(arg) && arg2 != 0)
+ else if (isInf(arg2) && fabs(arg) == 1)
result = NaN;
- else if (fabs(arg) == 1 && isInf(arg2))
- result = NaN;
- else if (arg2 == 0 && arg != 0)
- result = 1;
else
result = ::pow(arg, arg2);
break;
case ToString: {
double dradix = 10;
if (!args.isEmpty())
- dradix = args[0]->toInteger(exec);
+ dradix = args[0]->toIntegerPreserveNaN(exec);
if (dradix >= 2 && dradix <= 36 && dradix != 10) { // false for NaN
int radix = static_cast<int>(dradix);
const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
{
JSValue *fractionDigits = args[0];
double df = fractionDigits->toInteger(exec);
- if (fractionDigits->isUndefined())
- df = 0;
- if (!(df >= 0 && df <= 20)) // true for NaN
+ if (!(df >= 0 && df <= 20))
return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20");
int f = (int)df;
JSValue *fractionDigits = args[0];
double df = fractionDigits->toInteger(exec);
- if (!fractionDigits->isUndefined() && !(df >= 0 && df <= 20)) // true for NaN
+ if (!(df >= 0 && df <= 20))
return throwError(exec, RangeError, "toExponential() argument must between 0 and 20");
int f = (int)df;
int e = 0;
UString m;
- double dp = args[0]->toInteger(exec);
+ double dp = args[0]->toIntegerPreserveNaN(exec);
double x = v->toNumber(exec);
if (isNaN(dp) || isNaN(x) || isInf(x))
return jsString(v->toString(exec));
// handled above
break;
case CharAt:
- // Other browsers treat an omitted parameter as 0 rather than NaN.
- // That doesn't match the ECMA standard, but is needed for site compatibility.
- dpos = a0->isUndefined() ? 0 : a0->toInteger(exec);
- if (dpos >= 0 && dpos < len) // false for NaN
+ dpos = a0->toInteger(exec);
+ if (dpos >= 0 && dpos < len)
u = s.substr(static_cast<int>(dpos), 1);
else
u = "";
result = jsString(u);
break;
case CharCodeAt:
- // Other browsers treat an omitted parameter as 0 rather than NaN.
- // That doesn't match the ECMA standard, but is needed for site compatibility.
- dpos = a0->isUndefined() ? 0 : a0->toInteger(exec);
- if (dpos >= 0 && dpos < len) // false for NaN
+ dpos = a0->toInteger(exec);
+ if (dpos >= 0 && dpos < len)
result = jsNumber(s[static_cast<int>(dpos)].unicode());
else
result = jsNaN();
}
case IndexOf:
u2 = a0->toString(exec);
- if (a1->isUndefined())
+ dpos = a1->toInteger(exec);
+ if (dpos < 0)
dpos = 0;
- else {
- dpos = a1->toInteger(exec);
- if (dpos >= 0) { // false for NaN
- if (dpos > len)
- dpos = len;
- } else
- dpos = 0;
- }
+ else if (dpos > len)
+ dpos = len;
result = jsNumber(s.find(u2, static_cast<int>(dpos)));
break;
case LastIndexOf:
u2 = a0->toString(exec);
d = a1->toNumber(exec);
- if (a1->isUndefined() || KJS::isNaN(d))
+ dpos = a1->toIntegerPreserveNaN(exec);
+ if (dpos < 0)
+ dpos = 0;
+ else if (!(dpos <= len)) // true for NaN
dpos = len;
- else {
- dpos = a1->toInteger(exec);
- if (dpos >= 0) { // false for NaN
- if (dpos > len)
- dpos = len;
- } else
- dpos = 0;
- }
result = jsNumber(s.rfind(u2, static_cast<int>(dpos)));
break;
case Match:
}
break;
case Substr: {
- double d = a0->toInteger(exec);
- double d2 = a1->toInteger(exec);
- if (!(d >= 0)) { // true for NaN
- d += len;
- if (!(d >= 0)) // true for NaN
- d = 0;
- }
- if (isNaN(d2))
- d2 = len - d;
- else {
- if (d2 < 0)
- d2 = 0;
- if (d2 > len - d)
- d2 = len - d;
+ double start = a0->toInteger(exec);
+ double length = a1->isUndefined() ? len : a1->toInteger(exec);
+ if (start >= len)
+ return jsString("");
+ if (length < 0)
+ return jsString("");
+ if (start < 0) {
+ start += len;
+ if (start < 0)
+ start = 0;
}
- result = jsString(s.substr(static_cast<int>(d), static_cast<int>(d2)));
+ if (length > len - d)
+ length = len - d;
+ result = jsString(s.substr(static_cast<int>(start), static_cast<int>(length)));
break;
}
case Substring: {
UChar *p = buf;
ListIterator it = args.begin();
while (it != args.end()) {
- unsigned short u = it->toUInt16(exec);
+ unsigned short u = it->toUInt32(exec);
*p++ = UChar(u);
it++;
}
int32_t i;
if (getTruncatedInt32(i))
return i;
- return roundValue(exec, const_cast<JSValue*>(this));
+ double d = toNumber(exec);
+ return isNaN(d) ? 0.0 : trunc(d);
+}
+
+double JSValue::toIntegerPreserveNaN(ExecState *exec) const
+{
+ int32_t i;
+ if (getTruncatedInt32(i))
+ return i;
+ return trunc(toNumber(exec));
}
int32_t JSValue::toInt32SlowCase(ExecState* exec, bool& ok) const
{
ok = true;
- double d = roundValue(exec, const_cast<JSValue*>(this));
+ double d = toNumber(exec);
if (d >= -D32 / 2 && d < D32 / 2)
return static_cast<int32_t>(d);
ok = false;
return 0;
}
- double d32 = fmod(d, D32);
+ double d32 = fmod(trunc(d), D32);
if (d32 >= D32 / 2)
d32 -= D32;
else if (d32 < -D32 / 2)
d32 += D32;
-
return static_cast<int32_t>(d32);
}
{
ok = true;
- double d = roundValue(exec, const_cast<JSValue*>(this));
+ double d = toNumber(exec);
if (d >= 0.0 && d < D32)
return static_cast<uint32_t>(d);
ok = false;
return 0;
}
- double d32 = fmod(d, D32);
+ double d32 = fmod(trunc(d), D32);
if (d32 < 0)
d32 += D32;
-
return static_cast<uint32_t>(d32);
}
-uint16_t JSValue::toUInt16(ExecState *exec) const
-{
- uint32_t i;
- if (getTruncatedUInt32(i))
- return static_cast<uint16_t>(i);
-
- double d = roundValue(exec, const_cast<JSValue*>(this));
- if (d >= 0.0 && d < D16)
- return static_cast<uint16_t>(d);
-
- if (isNaN(d) || isInf(d))
- return 0;
- double d16 = fmod(d, D16);
-
- if (d16 < 0)
- d16 += D16;
-
- return static_cast<uint16_t>(d16);
-}
-
float JSValue::toFloat(ExecState* exec) const
{
return static_cast<float>(toNumber(exec));
// Integer conversions.
double toInteger(ExecState*) const;
+ double toIntegerPreserveNaN(ExecState*) const;
int32_t toInt32(ExecState*) const;
int32_t toInt32(ExecState*, bool& ok) const;
uint32_t toUInt32(ExecState*) const;
uint32_t toUInt32(ExecState*, bool& ok) const;
- uint16_t toUInt16(ExecState*) const;
// Floating point conversions.
float toFloat(ExecState*) const;
ALWAYS_INLINE int32_t JSValue::toInt32(ExecState* exec) const
{
int32_t i;
- if (JSImmediate::isImmediate(this) && JSImmediate::getTruncatedInt32(this, i))
+ if (getTruncatedInt32(i))
return i;
bool ok;
return toInt32SlowCase(exec, ok);
inline uint32_t JSValue::toUInt32(ExecState* exec) const
{
uint32_t i;
- if (JSImmediate::isImmediate(this) && JSImmediate::getTruncatedUInt32(this, i))
+ if (getTruncatedUInt32(i))
return i;
bool ok;
return toUInt32SlowCase(exec, ok);
inline int32_t JSValue::toInt32(ExecState* exec, bool& ok) const
{
int32_t i;
- if (JSImmediate::isImmediate(this) && JSImmediate::getTruncatedInt32(this, i)) {
+ if (getTruncatedInt32(i)) {
ok = true;
return i;
}
inline uint32_t JSValue::toUInt32(ExecState* exec, bool& ok) const
{
uint32_t i;
- if (JSImmediate::isImmediate(this) && JSImmediate::getTruncatedUInt32(this, i)) {
+ if (getTruncatedUInt32(i)) {
ok = true;
return i;
}
inline double round(double num) { return num > 0 ? floor(num + 0.5) : ceil(num - 0.5); }
inline float roundf(float num) { return num > 0 ? floorf(num + 0.5f) : ceilf(num - 0.5f); }
inline bool signbit(double num) { return _copysign(1.0, num) < 0; }
+inline double trunc(double num) { return num > 0 ? floor(num) : ceil(num); }
inline double nextafter(double x, double y) { return _nextafter(x, y); }
inline float nextafterf(float x, float y) { return x > y ? x - FLT_EPSILON : x + FLT_EPSILON; }
+2007-10-26 Darin Adler <darin@apple.com>
+
+ Reviewed by Maciej.
+
+ - test changes for http://bugs.webkit.org/show_bug.cgi?id=15703
+ fix numeric functions -- improve correctness and speed
+
+ * fast/js/resources/char-at.js: Updated test to expect that we get the first
+ character if we pass NaN to charAt and charCodeAt; it's what the specification
+ asks for and matches other browsers too.
+ * fast/js/char-at-expected.txt: Updated.
+
2007-10-26 Mark Rowe <mrowe@apple.com>
Update expected results.
PASS "x".charCodeAt(-1) is NaN
PASS "x".charAt(-Infinity) is ""
PASS "x".charCodeAt(-Infinity) is NaN
-PASS "x".charAt(NaN) is ""
-PASS "x".charCodeAt(NaN) is NaN
+PASS "x".charAt(NaN) is "x"
+PASS "x".charCodeAt(NaN) is 120
PASS "xy".charAt() is "x"
PASS "xy".charCodeAt() is 120
PASS "xy".charAt(undefined) is "x"
PASS "xy".charCodeAt(-1) is NaN
PASS "xy".charAt(-Infinity) is ""
PASS "xy".charCodeAt(-Infinity) is NaN
-PASS "xy".charAt(NaN) is ""
-PASS "xy".charCodeAt(NaN) is NaN
+PASS "xy".charAt(NaN) is "x"
+PASS "xy".charCodeAt(NaN) is 120
PASS successfullyParsed is true
TEST COMPLETE
['""', 'NaN'],
['""', 'NaN'],
['""', 'NaN'],
-['""', 'NaN'],
+['"x"', '120'],
['"x"', '120'],
['"x"', '120'],
['"x"', '120'],
['""', 'NaN'],
['""', 'NaN'],
['""', 'NaN'],
-['""', 'NaN']];
+['"x"', '120']];
for (var i = 0; i < cases.length; ++i)
{
var result = answers[i];
if (item[1] == "omitted") {
shouldBe('"' + item[0] + '".charAt()', result[0]);
- if (result[1] == 'NaN')
- shouldBeNaN('"' + item[0] + '".charCodeAt()');
- else
- shouldBe('"' + item[0] + '".charCodeAt()', result[1]);
+ if (result[1] == 'NaN')
+ shouldBeNaN('"' + item[0] + '".charCodeAt()');
+ else
+ shouldBe('"' + item[0] + '".charCodeAt()', result[1]);
} else {
shouldBe('"' + item[0] + '".charAt(' + item[1] + ')', result[0]);
- if (result[1] == 'NaN')
- shouldBeNaN('"' + item[0] + '".charCodeAt(' + item[1] + ')');
- else
- shouldBe('"' + item[0] + '".charCodeAt(' + item[1] + ')', result[1]);
+ if (result[1] == 'NaN')
+ shouldBeNaN('"' + item[0] + '".charCodeAt(' + item[1] + ')');
+ else
+ shouldBe('"' + item[0] + '".charCodeAt(' + item[1] + ')', result[1]);
}
}