KJS

number_object.cpp
1 // krazy:excludeall=doublequote_chars (UStrings aren't QStrings)
2 /*
3  * This file is part of the KDE libraries
4  * Copyright (C) 1999-2000,2003 Harri Porten ([email protected])
5  * Copyright (C) 2007 Apple Inc. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20  * USA
21  *
22  */
23 
24 #include "number_object.h"
25 #include "number_object.lut.h"
26 
27 #include "dtoa.h"
28 #include "error_object.h"
29 #include "operations.h"
30 #include <wtf/Assertions.h>
31 #include <wtf/MathExtras.h>
32 #include <wtf/Vector.h>
33 
34 namespace KJS
35 {
36 
37 // GCC cstring uses these automatically, but not all implementations do.
38 using std::strlen;
39 using std::strcpy;
40 using std::strncpy;
41 using std::memset;
42 using std::memcpy;
43 
44 static const double MAX_SAFE_INTEGER = 9007199254740991.0; //(2^53)-1
45 static const double MIN_SAFE_INTEGER = -9007199254740991.0; //-((2^53)-1)
46 
47 // ------------------------------ NumberInstance ----------------------------
48 
49 const ClassInfo NumberInstance::info = {"Number", nullptr, nullptr, nullptr};
50 
51 NumberInstance::NumberInstance(JSObject *proto)
52  : JSWrapperObject(proto)
53 {
54 }
55 
56 JSObject *NumberInstance::valueClone(Interpreter *targetCtx) const
57 {
58  NumberInstance *copy = new NumberInstance(targetCtx->builtinNumberPrototype());
59  copy->setInternalValue(internalValue());
60  return copy;
61 }
62 
63 // ------------------------------ NumberPrototype ---------------------------
64 
65 // ECMA 15.7.4
66 
67 NumberPrototype::NumberPrototype(ExecState *exec, ObjectPrototype *objProto, FunctionPrototype *funcProto)
68  : NumberInstance(objProto)
69 {
70  setInternalValue(jsNumber(0));
71 
72  // The constructor will be added later, after NumberObjectImp has been constructed
73 
74  putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToString, 1, exec->propertyNames().toString), DontEnum);
75  putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToLocaleString, 0, exec->propertyNames().toLocaleString), DontEnum);
76  putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ValueOf, 0, exec->propertyNames().valueOf), DontEnum);
77  putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToFixed, 1, exec->propertyNames().toFixed), DontEnum);
78  putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToExponential, 1, exec->propertyNames().toExponential), DontEnum);
79  putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToPrecision, 1, exec->propertyNames().toPrecision), DontEnum);
80 }
81 
82 // ------------------------------ NumberProtoFunc ---------------------------
83 
84 NumberProtoFunc::NumberProtoFunc(ExecState *exec, FunctionPrototype *funcProto, int i, int len, const Identifier &name)
85  : InternalFunctionImp(funcProto, name)
86  , id(i)
87 {
88  putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum);
89 }
90 
91 static UString integer_part_noexp(double d)
92 {
93  int decimalPoint;
94  int sign;
95  char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &sign, nullptr);
96  bool resultIsInfOrNan = (decimalPoint == 9999);
97  size_t length = strlen(result);
98 
99  UString str = sign ? "-" : "";
100  if (resultIsInfOrNan) {
101  str += result;
102  } else if (decimalPoint <= 0) {
103  str += "0";
104  } else {
105  Vector<char, 1024> buf(decimalPoint + 1);
106 
107  if (static_cast<int>(length) <= decimalPoint) {
108  strcpy(buf.data(), result);
109  memset(buf.data() + length, '0', decimalPoint - length);
110  } else {
111  strncpy(buf.data(), result, decimalPoint);
112  }
113 
114  buf[decimalPoint] = '\0';
115  str += UString(buf.data());
116  }
117 
118  kjs_freedtoa(result);
119 
120  return str;
121 }
122 
123 static UString char_sequence(char c, int count)
124 {
125  Vector<char, 2048> buf(count + 1, c);
126  buf[count] = '\0';
127 
128  return UString(buf.data());
129 }
130 
131 static double intPow10(int e)
132 {
133  // This function uses the "exponentiation by squaring" algorithm and
134  // long double to quickly and precisely calculate integer powers of 10.0.
135 
136  // This is a handy workaround for <rdar://problem/4494756>
137 
138  if (e == 0) {
139  return 1.0;
140  }
141 
142  bool negative = e < 0;
143  unsigned exp = negative ? -e : e;
144 
145  long double result = 10.0;
146  bool foundOne = false;
147  for (int bit = 31; bit >= 0; bit--) {
148  if (!foundOne) {
149  if ((exp >> bit) & 1) {
150  foundOne = true;
151  }
152  } else {
153  result = result * result;
154  if ((exp >> bit) & 1) {
155  result = result * 10.0;
156  }
157  }
158  }
159 
160  if (negative) {
161  return static_cast<double>(1.0 / result);
162  }
163  return static_cast<double>(result);
164 }
165 
166 static JSValue *numberToString(ExecState *exec, JSValue *v, const List &args)
167 {
168  double radixAsDouble = JSValue::toInteger(args[0], exec); // nan -> 0
169  if (radixAsDouble == 10 || JSValue::isUndefined(args[0])) {
170  return jsString(JSValue::toString(v, exec));
171  }
172 
173  if (radixAsDouble < 2 || radixAsDouble > 36) {
174  return throwError(exec, RangeError, "toString() radix argument must be between 2 and 36");
175  }
176 
177  int radix = static_cast<int>(radixAsDouble);
178  const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
179  // INT_MAX results in 1024 characters left of the dot with radix 2
180  // give the same space on the right side. safety checks are in place
181  // unless someone finds a precise rule.
182  char s[2048 + 3];
183  const char *lastCharInString = s + sizeof(s) - 1;
184  double x = JSValue::toNumber(v, exec);
185  if (isNaN(x) || isInf(x)) {
186  return jsString(UString::from(x));
187  }
188 
189  bool isNegative = x < 0.0;
190  if (isNegative) {
191  x = -x;
192  }
193 
194  double integerPart = floor(x);
195  char *decimalPoint = s + sizeof(s) / 2;
196 
197  // convert integer portion
198  char *p = decimalPoint;
199  double d = integerPart;
200  do {
201  int remainderDigit = static_cast<int>(fmod(d, radix));
202  *--p = digits[remainderDigit];
203  d /= radix;
204  } while ((d <= -1.0 || d >= 1.0) && s < p);
205 
206  if (isNegative) {
207  *--p = '-';
208  }
209  char *startOfResultString = p;
210  ASSERT(s <= startOfResultString);
211 
212  d = x - integerPart;
213  p = decimalPoint;
214  const double epsilon = 0.001; // TODO: guessed. base on radix ?
215  bool hasFractionalPart = (d < -epsilon || d > epsilon);
216  if (hasFractionalPart) {
217  *p++ = '.';
218  do {
219  d *= radix;
220  const int digit = static_cast<int>(d);
221  *p++ = digits[digit];
222  d -= digit;
223  } while ((d < -epsilon || d > epsilon) && p < lastCharInString);
224  }
225  *p = '\0';
226  ASSERT(p < s + sizeof(s));
227 
228  return jsString(startOfResultString);
229 }
230 
231 static JSValue *numberToFixed(ExecState *exec, JSValue *v, const List &args)
232 {
233  JSValue *fractionDigits = args[0];
234  double df = JSValue::toInteger(fractionDigits, exec);
235  if (!(df >= 0 && df <= 20)) {
236  return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20");
237  }
238  int f = (int)df;
239 
240  double x = JSValue::toNumber(v, exec);
241  if (isNaN(x)) {
242  return jsString("NaN");
243  }
244 
245  UString s;
246  if (x < 0) {
247  s.append('-');
248  x = -x;
249  } else if (x == -0.0) {
250  x = 0;
251  }
252 
253  if (x >= pow(10.0, 21.0)) {
254  return jsString(s + UString::from(x));
255  }
256 
257  const double tenToTheF = pow(10.0, f);
258  double n = floor(x * tenToTheF);
259  if (fabs(n / tenToTheF - x) >= fabs((n + 1) / tenToTheF - x)) {
260  n++;
261  }
262 
263  UString m = integer_part_noexp(n);
264 
265  int k = m.size();
266  if (k <= f) {
267  UString z;
268  for (int i = 0; i < f + 1 - k; i++) {
269  z.append('0');
270  }
271  m = z + m;
272  k = f + 1;
273  ASSERT(k == m.size());
274  }
275  int kMinusf = k - f;
276  if (kMinusf < m.size()) {
277  return jsString(s + m.substr(0, kMinusf) + "." + m.substr(kMinusf));
278  }
279  return jsString(s + m.substr(0, kMinusf));
280 }
281 
282 void fractionalPartToString(char *buf, int &i, const char *result, int resultLength, int fractionalDigits)
283 {
284  if (fractionalDigits <= 0) {
285  return;
286  }
287 
288  int fDigitsInResult = static_cast<int>(resultLength) - 1;
289  buf[i++] = '.';
290  if (fDigitsInResult > 0) {
291  if (fractionalDigits < fDigitsInResult) {
292  strncpy(buf + i, result + 1, fractionalDigits);
293  i += fractionalDigits;
294  } else {
295  strcpy(buf + i, result + 1);
296  i += static_cast<int>(resultLength) - 1;
297  }
298  }
299 
300  for (int j = 0; j < fractionalDigits - fDigitsInResult; j++) {
301  buf[i++] = '0';
302  }
303 }
304 
305 void exponentialPartToString(char *buf, int &i, int decimalPoint)
306 {
307  buf[i++] = 'e';
308  buf[i++] = (decimalPoint >= 0) ? '+' : '-';
309  // decimalPoint can't be more than 3 digits decimal given the
310  // nature of float representation
311  int exponential = decimalPoint - 1;
312  if (exponential < 0) {
313  exponential *= -1;
314  }
315  if (exponential >= 100) {
316  buf[i++] = static_cast<char>('0' + exponential / 100);
317  }
318  if (exponential >= 10) {
319  buf[i++] = static_cast<char>('0' + (exponential % 100) / 10);
320  }
321  buf[i++] = static_cast<char>('0' + exponential % 10);
322 }
323 
324 static JSValue *numberToExponential(ExecState *exec, JSValue *v, const List &args)
325 {
326  double x = JSValue::toNumber(v, exec);
327 
328  if (isNaN(x) || isInf(x)) {
329  return jsString(UString::from(x));
330  }
331 
332  JSValue *fractionalDigitsValue = args[0];
333  double df = JSValue::toInteger(fractionalDigitsValue, exec);
334  if (!(df >= 0 && df <= 20)) {
335  return throwError(exec, RangeError, "toExponential() argument must between 0 and 20");
336  }
337  int fractionalDigits = (int)df;
338  bool includeAllDigits = JSValue::isUndefined(fractionalDigitsValue);
339 
340  int decimalAdjust = 0;
341  if (x && !includeAllDigits) {
342  double logx = floor(log10(fabs(x)));
343  x /= pow(10.0, logx);
344  const double tenToTheF = pow(10.0, fractionalDigits);
345  double fx = floor(x * tenToTheF) / tenToTheF;
346  double cx = ceil(x * tenToTheF) / tenToTheF;
347 
348  if (fabs(fx - x) < fabs(cx - x)) {
349  x = fx;
350  } else {
351  x = cx;
352  }
353 
354  decimalAdjust = static_cast<int>(logx);
355  }
356 
357  if (isNaN(x)) {
358  return jsString("NaN");
359  }
360 
361  if (x == -0.0) { // (-0.0).toExponential() should print as 0 instead of -0
362  x = 0;
363  }
364 
365  int decimalPoint;
366  int sign;
367  char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, nullptr);
368  size_t resultLength = strlen(result);
369  decimalPoint += decimalAdjust;
370 
371  int i = 0;
372  char buf[80]; // digit + '.' + fractionDigits (max 20) + 'e' + sign + exponent (max?)
373  if (sign) {
374  buf[i++] = '-';
375  }
376 
377  if (decimalPoint == 999) { // ? 9999 is the magical "result is Inf or NaN" value. what's 999??
378  strcpy(buf + i, result);
379  } else {
380  buf[i++] = result[0];
381 
382  if (includeAllDigits) {
383  fractionalDigits = static_cast<int>(resultLength) - 1;
384  }
385 
386  fractionalPartToString(buf, i, result, resultLength, fractionalDigits);
387  exponentialPartToString(buf, i, decimalPoint);
388  buf[i++] = '\0';
389  }
390  ASSERT(i <= 80);
391 
392  kjs_freedtoa(result);
393 
394  return jsString(buf);
395 }
396 
397 static JSValue *numberToPrecision(ExecState *exec, JSValue *v, const List &args)
398 {
399  double doublePrecision = JSValue::toIntegerPreserveNaN(args[0], exec);
400  double x = JSValue::toNumber(v, exec);
401  if (JSValue::isUndefined(args[0]) || isNaN(x) || isInf(x)) {
402  return jsString(JSValue::toString(v, exec));
403  }
404 
405  UString s;
406  if (x < 0) {
407  s = "-";
408  x = -x;
409  }
410 
411  if (!(doublePrecision >= 1 && doublePrecision <= 21)) { // true for NaN
412  return throwError(exec, RangeError, "toPrecision() argument must be between 1 and 21");
413  }
414  int precision = (int)doublePrecision;
415 
416  int e = 0;
417  UString m;
418  if (x) {
419  e = static_cast<int>(log10(x));
420  double tens = intPow10(e - precision + 1);
421  double n = floor(x / tens);
422  if (n < intPow10(precision - 1)) {
423  e = e - 1;
424  tens = intPow10(e - precision + 1);
425  n = floor(x / tens);
426  }
427 
428  if (fabs((n + 1.0) * tens - x) <= fabs(n * tens - x)) {
429  ++n;
430  }
431  // maintain n < 10^(precision)
432  if (n >= intPow10(precision)) {
433  n /= 10.0;
434  e += 1;
435  }
436  ASSERT(intPow10(precision - 1) <= n);
437  ASSERT(n < intPow10(precision));
438 
439  m = integer_part_noexp(n);
440  if (e < -6 || e >= precision) {
441  if (m.size() > 1) {
442  m = m.substr(0, 1) + "." + m.substr(1);
443  }
444  if (e >= 0) {
445  return jsString(s + m + "e+" + UString::from(e));
446  }
447  return jsString(s + m + "e-" + UString::from(-e));
448  }
449  } else {
450  m = char_sequence('0', precision);
451  e = 0;
452  }
453 
454  if (e == precision - 1) {
455  return jsString(s + m);
456  } else if (e >= 0) {
457  if (e + 1 < m.size()) {
458  return jsString(s + m.substr(0, e + 1) + "." + m.substr(e + 1));
459  }
460  return jsString(s + m);
461  }
462  return jsString(s + "0." + char_sequence('0', -(e + 1)) + m);
463 }
464 
465 // ECMA 15.7.4.2 - 15.7.4.7
466 JSValue *NumberProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
467 {
468  // no generic function. "this" has to be a Number object
469  if (!thisObj->inherits(&NumberInstance::info)) {
470  return throwError(exec, TypeError);
471  }
472 
473  JSValue *v = static_cast<NumberInstance *>(thisObj)->internalValue();
474  switch (id) {
475  case ToString:
476  return numberToString(exec, v, args);
477  case ToLocaleString: /* TODO */
478  return jsString(JSValue::toString(v, exec));
479  case ValueOf:
480  return jsNumber(JSValue::toNumber(v, exec));
481  case ToFixed:
482  return numberToFixed(exec, v, args);
483  case ToExponential:
484  return numberToExponential(exec, v, args);
485  case ToPrecision:
486  return numberToPrecision(exec, v, args);
487  }
488  return nullptr;
489 }
490 
491 // ------------------------------ NumberObjectImp ------------------------------
492 
493 const ClassInfo NumberObjectImp::info = {"Function", &InternalFunctionImp::info, &numberTable, nullptr};
494 
495 /* Source for number_object.lut.h
496 @begin numberTable 5
497  NaN NumberObjectImp::NaNValue DontEnum|DontDelete|ReadOnly
498  NEGATIVE_INFINITY NumberObjectImp::NegInfinity DontEnum|DontDelete|ReadOnly
499  POSITIVE_INFINITY NumberObjectImp::PosInfinity DontEnum|DontDelete|ReadOnly
500  MAX_VALUE NumberObjectImp::MaxValue DontEnum|DontDelete|ReadOnly
501  MIN_VALUE NumberObjectImp::MinValue DontEnum|DontDelete|ReadOnly
502 
503  MAX_SAFE_INTEGER NumberObjectImp::MaxSafeInteger DontEnum|DontDelete|ReadOnly
504  MIN_SAFE_INTEGER NumberObjectImp::MinSafeInteger DontEnum|DontDelete|ReadOnly
505  isFinite NumberObjectImp::IsFinite DontEnum|Function 1
506  isInteger NumberObjectImp::IsInteger DontEnum|Function 1
507  isNaN NumberObjectImp::IsNaN DontEnum|Function 1
508  isSafeInteger NumberObjectImp::IsSafeInteger DontEnum|Function 1
509  parseInt NumberObjectImp::ParseInt DontEnum|Function 2
510  parseFloat NumberObjectImp::ParseFloat DontEnum|Function 1
511 @end
512 */
513 NumberObjectImp::NumberObjectImp(ExecState *exec, FunctionPrototype *funcProto, NumberPrototype *numberProto)
514  : InternalFunctionImp(funcProto)
515 {
516  // Number.Prototype
517  putDirect(exec->propertyNames().prototype, numberProto, DontEnum | DontDelete | ReadOnly);
518 
519  // no. of arguments for constructor
520  putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum);
521 }
522 
523 bool NumberObjectImp::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot)
524 {
525  return getStaticPropertySlot<NumberFuncImp, NumberObjectImp, InternalFunctionImp>(exec, &numberTable, this, propertyName, slot);
526 }
527 
528 JSValue *NumberObjectImp::getValueProperty(ExecState *, int token) const
529 {
530  // ECMA 15.7.3
531  switch (token) {
532  case NaNValue:
533  return jsNaN();
534  case NegInfinity:
535  return jsNumberCell(-Inf);
536  case PosInfinity:
537  return jsNumberCell(Inf);
538  case MaxValue:
539  return jsNumberCell(1.7976931348623157E+308);
540  case MinValue:
541  return jsNumberCell(5E-324);
542  case MaxSafeInteger:
543  return jsNumber(MAX_SAFE_INTEGER);
544  case MinSafeInteger:
545  return jsNumber(MIN_SAFE_INTEGER);
546  }
547  return jsNull();
548 }
549 
550 bool NumberObjectImp::implementsConstruct() const
551 {
552  return true;
553 }
554 
555 // ECMA 15.7.1
556 JSObject *NumberObjectImp::construct(ExecState *exec, const List &args)
557 {
558  JSObject *proto = exec->lexicalInterpreter()->builtinNumberPrototype();
559  NumberInstance *obj = new NumberInstance(proto);
560 
561  double n = args.isEmpty() ? 0 : JSValue::toNumber(args[0], exec);
562  obj->setInternalValue(jsNumber(n));
563  return obj;
564 }
565 
566 // ECMA 15.7.2
567 JSValue *NumberObjectImp::callAsFunction(ExecState *exec, JSObject *, const List &args)
568 {
569  double n = args.isEmpty() ? 0 : JSValue::toNumber(args[0], exec);
570  return jsNumber(n);
571 }
572 
573 NumberFuncImp::NumberFuncImp(ExecState* exec, int i, int l, const Identifier& name)
574  : InternalFunctionImp(static_cast<FunctionPrototype*>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name)
575  , id(i)
576 {
577  putDirect(exec->propertyNames().length, l, DontDelete|ReadOnly|DontEnum);
578 }
579 
580 JSValue* NumberFuncImp::callAsFunction(ExecState* exec, JSObject* /*thisObj*/, const List& args)
581 {
582  double arg = JSValue::toNumber(args[0], exec);
583 
584  switch (id) {
585  case NumberObjectImp::IsFinite:
586  if (JSValue::type(args[0]) != NumberType)
587  return jsBoolean(false);
588  return jsBoolean(!isNaN(arg) && !isInf(arg));
589 
590  case NumberObjectImp::IsInteger:
591  {
592  if (JSValue::type(args[0]) != NumberType)
593  return jsBoolean(false);
594  if (isNaN(arg) || isInf(arg))
595  return jsBoolean(false);
596  double num = JSValue::toInteger(args[0], exec);
597  return jsBoolean(num == arg);
598  }
599  case NumberObjectImp::IsNaN:
600  if (JSValue::type(args[0]) != NumberType)
601  return jsBoolean(false);
602  return jsBoolean(isNaN(arg));
603 
604  case NumberObjectImp::IsSafeInteger:
605  {
606  if (JSValue::type(args[0]) != NumberType)
607  return jsBoolean(false);
608  if (isNaN(arg) || isInf(arg))
609  return jsBoolean(false);
610  double num = JSValue::toInteger(args[0], exec);
611  if (num != arg)
612  return jsBoolean(false);
613  return jsBoolean(fabs(num) <= MAX_SAFE_INTEGER);
614  }
615  case NumberObjectImp::ParseInt:
616  return jsNumber(KJS::parseInt(JSValue::toString(args[0], exec), JSValue::toInt32(args[1], exec)));
617  case NumberObjectImp::ParseFloat:
618  return jsNumber(KJS::parseFloat(JSValue::toString(args[0], exec)));
619  }
620  return jsUndefined();
621 }
622 
623 } // namespace KJS
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
static UString from(int i)
Constructs a string from an int.
Definition: ustring.cpp:559
KDB_EXPORT QString numberToString(double value, int decimalPlaces)
const char * name(StandardAction id)
virtual QVariant callAsFunction(ScriptableExtension *callerPrincipal, quint64 objId, const ArgList &args)
bool isNaN(double x)
bool isInf(double x)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Mar 27 2023 04:10:39 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.