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