1 /* Digital Mars DMDScript source code. 2 * Copyright (c) 2000-2002 by Chromium Communications 3 * D version Copyright (c) 2004-2010 by Digital Mars 4 * Distributed under the Boost Software License, Version 1.0. 5 * (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 * written by Walter Bright 7 * http://www.digitalmars.com 8 * 9 * D2 port by Dmitry Olshansky 10 * 11 * DMDScript is implemented in the D Programming Language, 12 * http://www.digitalmars.com/d/ 13 * 14 * For a C++ implementation of DMDScript, including COM support, see 15 * http://www.digitalmars.com/dscript/cppscript.html 16 */ 17 18 module dmdscript.dnumber; 19 20 import std.math; 21 import std.c.stdlib; 22 import std.exception; 23 24 import dmdscript.script; 25 import dmdscript.dobject; 26 import dmdscript.dfunction; 27 import dmdscript.value; 28 import dmdscript.threadcontext; 29 import dmdscript.text; 30 import dmdscript.property; 31 import dmdscript.errmsgs; 32 import dmdscript.dnative; 33 34 /* ===================== Dnumber_constructor ==================== */ 35 36 class DnumberConstructor : Dfunction 37 { 38 this() 39 { 40 super(1, Dfunction_prototype); 41 uint attributes = DontEnum | DontDelete | ReadOnly; 42 43 name = TEXT_Number; 44 Put(TEXT_MAX_VALUE, d_number.max, attributes); 45 Put(TEXT_MIN_VALUE, d_number.min_normal*d_number.epsilon, attributes); 46 Put(TEXT_NaN, d_number.nan, attributes); 47 Put(TEXT_NEGATIVE_INFINITY, -d_number.infinity, attributes); 48 Put(TEXT_POSITIVE_INFINITY, d_number.infinity, attributes); 49 } 50 51 override void* Construct(CallContext *cc, Value *ret, Value[] arglist) 52 { 53 // ECMA 15.7.2 54 d_number n; 55 Dobject o; 56 57 n = (arglist.length) ? arglist[0].toNumber() : 0; 58 o = new Dnumber(n); 59 ret.putVobject(o); 60 return null; 61 } 62 63 override void* Call(CallContext *cc, Dobject othis, Value* ret, Value[] arglist) 64 { 65 // ECMA 15.7.1 66 d_number n; 67 68 n = (arglist.length) ? arglist[0].toNumber() : 0; 69 ret.putVnumber(n); 70 return null; 71 } 72 } 73 74 75 /* ===================== Dnumber_prototype_toString =============== */ 76 77 void* Dnumber_prototype_toString(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 78 { 79 // ECMA v3 15.7.4.2 80 d_string s; 81 82 // othis must be a Number 83 if(!othis.isClass(TEXT_Number)) 84 { 85 ret.putVundefined(); 86 ErrInfo errinfo; 87 return Dobject.RuntimeError(&errinfo, 88 errmsgtbl[ERR_FUNCTION_WANTS_NUMBER], 89 TEXT_toString, 90 othis.classname); 91 } 92 else 93 { 94 Value* v; 95 96 v = &(cast(Dnumber)othis).value; 97 98 if(arglist.length) 99 { 100 d_number radix; 101 102 radix = arglist[0].toNumber(); 103 if(radix == 10.0 || arglist[0].isUndefined()) 104 s = v.toString(); 105 else 106 { 107 int r; 108 109 r = cast(int)radix; 110 // radix must be an integer 2..36 111 if(r == radix && r >= 2 && r <= 36) 112 s = v.toString(r); 113 else 114 s = v.toString(); 115 } 116 } 117 else 118 s = v.toString(); 119 ret.putVstring(s); 120 } 121 return null; 122 } 123 124 /* ===================== Dnumber_prototype_toLocaleString =============== */ 125 126 void* Dnumber_prototype_toLocaleString(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 127 { 128 // ECMA v3 15.7.4.3 129 d_string s; 130 131 // othis must be a Number 132 if(!othis.isClass(TEXT_Number)) 133 { 134 ret.putVundefined(); 135 ErrInfo errinfo; 136 return Dobject.RuntimeError(&errinfo, 137 errmsgtbl[ERR_FUNCTION_WANTS_NUMBER], 138 TEXT_toLocaleString, 139 othis.classname); 140 } 141 else 142 { 143 Value* v; 144 145 v = &(cast(Dnumber)othis).value; 146 147 s = v.toLocaleString(); 148 ret.putVstring(s); 149 } 150 return null; 151 } 152 153 /* ===================== Dnumber_prototype_valueOf =============== */ 154 155 void* Dnumber_prototype_valueOf(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 156 { 157 // othis must be a Number 158 if(!othis.isClass(TEXT_Number)) 159 { 160 ret.putVundefined(); 161 ErrInfo errinfo; 162 return Dobject.RuntimeError(&errinfo, 163 errmsgtbl[ERR_FUNCTION_WANTS_NUMBER], 164 TEXT_valueOf, 165 othis.classname); 166 } 167 else 168 { 169 Value* v; 170 171 v = &(cast(Dnumber)othis).value; 172 Value.copy(ret, v); 173 } 174 return null; 175 } 176 177 /* ===================== Formatting Support =============== */ 178 179 const int FIXED_DIGITS = 20; // ECMA says >= 20 180 181 182 // power of tens array, indexed by power 183 184 static d_number tens[FIXED_DIGITS + 1] = 185 [ 186 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 187 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 188 1e20, 189 ]; 190 191 /************************************************ 192 * Let e and n be integers such that 193 * 10**f <= n < 10**(f+1) and for which the exact 194 * mathematical value of n * 10**(e-f) - x is as close 195 * to zero as possible. If there are two such sets of 196 * e and n, pick the e and n for which n * 10**(e-f) 197 * is larger. 198 */ 199 200 number_t deconstruct_real(d_number x, int f, out int pe) 201 { 202 number_t n; 203 int e; 204 int i; 205 206 e = cast(int)log10(x); 207 i = e - f; 208 if(i >= 0 && i < tens.length) 209 // table lookup for speed & accuracy 210 n = cast(number_t)(x / tens[i] + 0.5); 211 else 212 n = cast(number_t)(x / std.math.pow(cast(real)10.0, i) + 0.5); 213 214 pe = e; 215 return n; 216 } 217 218 /* ===================== Dnumber_prototype_toFixed =============== */ 219 220 void* Dnumber_prototype_toFixed(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 221 { 222 // ECMA v3 15.7.4.5 223 Value* v; 224 d_number x; 225 d_number fractionDigits; 226 d_string result; 227 int dup; 228 229 if(arglist.length) 230 { 231 v = &arglist[0]; 232 fractionDigits = v.toInteger(); 233 } 234 else 235 fractionDigits = 0; 236 if(fractionDigits < 0 || fractionDigits > FIXED_DIGITS) 237 { 238 ErrInfo errinfo; 239 240 ret.putVundefined(); 241 return Dobject.RangeError(&errinfo, ERR_VALUE_OUT_OF_RANGE, 242 TEXT_toFixed, "fractionDigits"); 243 } 244 v = &othis.value; 245 x = v.toNumber(); 246 if(isnan(x)) 247 { 248 result = TEXT_NaN; // return "NaN" 249 } 250 else 251 { 252 int sign; 253 char[] m; 254 255 sign = 0; 256 if(x < 0) 257 { 258 sign = 1; 259 x = -x; 260 } 261 if(x >= 1.0e+21) // exponent must be FIXED_DIGITS+1 262 { 263 Value vn; 264 vn.putVnumber(x); 265 ret.putVstring(vn.toString()); 266 return null; 267 } 268 else 269 { 270 number_t n; 271 tchar buffer[32 + 1]; 272 d_number tenf; 273 int f; 274 275 f = cast(int)fractionDigits; 276 tenf = tens[f]; // tenf = 10**f 277 278 // Compute n which gives |(n / tenf) - x| is the smallest 279 // value. If there are two such n's, pick the larger. 280 n = cast(number_t)(x * tenf + 0.5); // round up & chop 281 282 if(n == 0) 283 { 284 m = cast(char[])"0"; //TODO: try hacking this func to be clean ;) 285 dup = 0; 286 } 287 else 288 { 289 // n still doesn't give 20 digits, only 19 290 m = std..string.sformat(buffer[], "%d", cast(ulong)n); 291 dup = 1; 292 } 293 if(f != 0) 294 { 295 ptrdiff_t i; 296 ptrdiff_t k; 297 k = m.length; 298 if(k <= f) 299 { 300 tchar* s; 301 ptrdiff_t nzeros; 302 303 s = cast(tchar*)alloca((f + 1) * tchar.sizeof); 304 assert(s); 305 nzeros = f + 1 - k; 306 s[0 .. nzeros] = '0'; 307 s[nzeros .. f + 1] = m[0 .. k]; 308 309 m = s[0 .. f + 1]; 310 k = f + 1; 311 } 312 313 // res = "-" + m[0 .. k-f] + "." + m[k-f .. k]; 314 char[] res = new tchar[sign + k + 1]; 315 if(sign) 316 res[0] = '-'; 317 i = k - f; 318 res[sign .. sign + i] = m[0 .. i]; 319 res[sign + i] = '.'; 320 res[sign + i + 1 .. sign + k + 1] = m[i .. k]; 321 result = assumeUnique(res); 322 goto Ldone; 323 //+++ end of patch ++++ 324 } 325 } 326 if(sign) 327 result = TEXT_dash ~ m.idup; // TODO: remove idup somehow 328 else if(dup) 329 result = m.idup; 330 else 331 result = assumeUnique(m); 332 } 333 334 Ldone: 335 ret.putVstring(result); 336 return null; 337 } 338 339 /* ===================== Dnumber_prototype_toExponential =============== */ 340 341 void* Dnumber_prototype_toExponential(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 342 { 343 // ECMA v3 15.7.4.6 344 Value* varg; 345 Value* v; 346 d_number x; 347 d_number fractionDigits; 348 d_string result; 349 350 if(arglist.length) 351 { 352 varg = &arglist[0]; 353 fractionDigits = varg.toInteger(); 354 }else 355 fractionDigits = FIXED_DIGITS; 356 v = &othis.value; 357 x = v.toNumber(); 358 if(isnan(x)) 359 { 360 result = TEXT_NaN; // return "NaN" 361 } 362 else 363 { 364 int sign; 365 366 sign = 0; 367 if(x < 0) 368 { 369 sign = 1; 370 x = -x; 371 } 372 if(std.math.isinf(x)) 373 { 374 result = sign ? TEXT_negInfinity : TEXT_Infinity; 375 } 376 else 377 { 378 int f; 379 number_t n; 380 int e; 381 tchar[] m; 382 int i; 383 tchar buffer[32 + 1]; 384 385 if(fractionDigits < 0 || fractionDigits > FIXED_DIGITS) 386 { 387 ErrInfo errinfo; 388 389 ret.putVundefined(); 390 return Dobject.RangeError(&errinfo, 391 ERR_VALUE_OUT_OF_RANGE, 392 TEXT_toExponential, 393 "fractionDigits"); 394 } 395 396 f = cast(int)fractionDigits; 397 if(x == 0) 398 { 399 tchar* s; 400 401 s = cast(tchar*)alloca((f + 1) * tchar.sizeof); 402 assert(s); 403 m = s[0 .. f + 1]; 404 m[0 .. f + 1] = '0'; 405 e = 0; 406 } 407 else 408 { 409 if(arglist.length && !varg.isUndefined()) 410 { 411 /* Step 12 412 * Let e and n be integers such that 413 * 10**f <= n < 10**(f+1) and for which the exact 414 * mathematical value of n * 10**(e-f) - x is as close 415 * to zero as possible. If there are two such sets of 416 * e and n, pick the e and n for which n * 10**(e-f) 417 * is larger. 418 * [Note: this is the same as Step 15 in toPrecision() 419 * with f = p - 1] 420 */ 421 n = deconstruct_real(x, f, e); 422 } 423 else 424 { 425 /* Step 19 426 * Let e, n, and f be integers such that f >= 0, 427 * 10**f <= n < 10**(f+1), the number value for 428 * n * 10**(e-f) is x, and f is as small as possible. 429 * Note that the decimal representation of n has f+1 430 * digits, n is not divisible by 10, and the least 431 * significant digit of n is not necessarilly uniquely 432 * determined by these criteria. 433 */ 434 /* Implement by trying maximum digits, and then 435 * lopping off trailing 0's. 436 */ 437 f = 19; // should use FIXED_DIGITS 438 n = deconstruct_real(x, f, e); 439 440 // Lop off trailing 0's 441 assert(n); 442 while((n % 10) == 0) 443 { 444 n /= 10; 445 f--; 446 assert(f >= 0); 447 } 448 } 449 // n still doesn't give 20 digits, only 19 450 m = std..string.sformat(buffer[], "%d", cast(ulong)n); 451 } 452 if(f) 453 { 454 tchar* s; 455 456 // m = m[0] + "." + m[1 .. f+1]; 457 s = cast(tchar*)alloca((f + 2) * tchar.sizeof); 458 assert(s); 459 s[0] = m[0]; 460 s[1] = '.'; 461 s[2 .. f + 2] = m[1 .. f + 1]; 462 m = s[0 .. f + 2]; 463 } 464 465 // result = sign + m + "e" + c + e; 466 d_string c = (e >= 0) ? "+" : ""; 467 468 result = std..string.format("%s%se%s%d", sign ? "-" : "", m, c, e); 469 } 470 } 471 472 ret.putVstring(result); 473 return null; 474 } 475 476 /* ===================== Dnumber_prototype_toPrecision =============== */ 477 478 void* Dnumber_prototype_toPrecision(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 479 { 480 // ECMA v3 15.7.4.7 481 Value* varg; 482 Value* v; 483 d_number x; 484 d_number precision; 485 d_string result; 486 487 v = &othis.value; 488 x = v.toNumber(); 489 490 varg = (arglist.length == 0) ? &vundefined : &arglist[0]; 491 492 if(arglist.length == 0 || varg.isUndefined()) 493 { 494 Value vn; 495 496 vn.putVnumber(x); 497 result = vn.toString(); 498 } 499 else 500 { 501 if(isnan(x)) 502 result = TEXT_NaN; 503 else 504 { 505 int sign; 506 int e; 507 int p; 508 int i; 509 tchar[] m; 510 number_t n; 511 tchar buffer[32 + 1]; 512 513 sign = 0; 514 if(x < 0) 515 { 516 sign = 1; 517 x = -x; 518 } 519 520 if(std.math.isinf(x)) 521 { 522 result = sign ? TEXT_negInfinity : TEXT_Infinity; 523 goto Ldone; 524 } 525 526 precision = varg.toInteger(); 527 if(precision < 1 || precision > 21) 528 { 529 ErrInfo errinfo; 530 531 ret.putVundefined(); 532 return Dobject.RangeError(&errinfo, 533 ERR_VALUE_OUT_OF_RANGE, 534 TEXT_toPrecision, 535 "precision"); 536 } 537 538 p = cast(int)precision; 539 if(x != 0) 540 { 541 /* Step 15 542 * Let e and n be integers such that 10**(p-1) <= n < 10**p 543 * and for which the exact mathematical value of n * 10**(e-p+1) - x 544 * is as close to zero as possible. If there are two such sets 545 * of e and n, pick the e and n for which n * 10**(e-p+1) is larger. 546 */ 547 n = deconstruct_real(x, p - 1, e); 548 549 // n still doesn't give 20 digits, only 19 550 m = std..string.sformat(buffer[], "%d", cast(ulong)n); 551 552 if(e < -6 || e >= p) 553 { 554 // result = sign + m[0] + "." + m[1 .. p] + "e" + c + e; 555 d_string c = (e >= 0) ? "+" : ""; 556 result = std..string.format("%s%s.%se%s%d", 557 (sign ? "-" : ""), m[0], m[1 .. $], c, e); 558 goto Ldone; 559 } 560 } 561 else 562 { 563 // Step 12 564 // m = array[p] of '0' 565 tchar* s; 566 s = cast(tchar*)alloca(p * tchar.sizeof); 567 assert(s); 568 m = s[0 .. p]; 569 m[] = '0'; 570 571 e = 0; 572 } 573 if(e != p - 1) 574 { 575 tchar* s; 576 577 if(e >= 0) 578 { 579 // m = m[0 .. e+1] + "." + m[e+1 .. p]; 580 581 s = cast(tchar*)alloca((p + 1) * tchar.sizeof); 582 assert(s); 583 i = e + 1; 584 s[0 .. i] = m[0 .. i]; 585 s[i] = '.'; 586 s[i + 1 .. p + 1] = m[i .. p]; 587 m = s[0 .. p + 1]; 588 } 589 else 590 { 591 // m = "0." + (-(e+1) occurrences of the character '0') + m; 592 int imax = 2 + - (e + 1); 593 594 s = cast(tchar*)alloca((imax + p) * tchar.sizeof); 595 assert(s); 596 s[0] = '0'; 597 s[1] = '.'; 598 s[2 .. imax] = '0'; 599 s[imax .. imax + p] = m[0 .. p]; 600 m = s[0 .. imax + p]; 601 } 602 } 603 if(sign) 604 result = TEXT_dash ~ m.idup; //TODO: remove idup somehow 605 else 606 result = m.idup; 607 } 608 } 609 610 Ldone: 611 ret.putVstring(result); 612 return null; 613 } 614 615 /* ===================== Dnumber_prototype ==================== */ 616 617 class DnumberPrototype : Dnumber 618 { 619 this() 620 { 621 super(Dobject_prototype); 622 uint attributes = DontEnum; 623 624 Dobject f = Dfunction_prototype; 625 626 Put(TEXT_constructor, Dnumber_constructor, attributes); 627 628 static enum NativeFunctionData nfd[] = 629 [ 630 { TEXT_toString, &Dnumber_prototype_toString, 1 }, 631 // Permissible to use toString() 632 { TEXT_toLocaleString, &Dnumber_prototype_toLocaleString, 1 }, 633 { TEXT_valueOf, &Dnumber_prototype_valueOf, 0 }, 634 { TEXT_toFixed, &Dnumber_prototype_toFixed, 1 }, 635 { TEXT_toExponential, &Dnumber_prototype_toExponential, 1 }, 636 { TEXT_toPrecision, &Dnumber_prototype_toPrecision, 1 }, 637 ]; 638 639 DnativeFunction.initialize(this, nfd, attributes); 640 } 641 } 642 643 644 /* ===================== Dnumber ==================== */ 645 646 class Dnumber : Dobject 647 { 648 this(d_number n) 649 { 650 super(getPrototype()); 651 classname = TEXT_Number; 652 value.putVnumber(n); 653 } 654 655 this(Dobject prototype) 656 { 657 super(prototype); 658 classname = TEXT_Number; 659 value.putVnumber(0); 660 } 661 662 static Dfunction getConstructor() 663 { 664 return Dnumber_constructor; 665 } 666 667 static Dobject getPrototype() 668 { 669 return Dnumber_prototype; 670 } 671 672 static void initialize() 673 { 674 Dnumber_constructor = new DnumberConstructor(); 675 Dnumber_prototype = new DnumberPrototype(); 676 677 Dnumber_constructor.Put(TEXT_prototype, Dnumber_prototype, DontEnum | DontDelete | ReadOnly); 678 } 679 } 680