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() 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 immutable d_number[FIXED_DIGITS + 1] tens = 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 import std.format : sformat; 223 224 // ECMA v3 15.7.4.5 225 Value* v; 226 d_number x; 227 d_number fractionDigits; 228 d_string result; 229 int dup; 230 231 if(arglist.length) 232 { 233 v = &arglist[0]; 234 fractionDigits = v.toInteger(); 235 } 236 else 237 fractionDigits = 0; 238 if(fractionDigits < 0 || fractionDigits > FIXED_DIGITS) 239 { 240 ErrInfo errinfo; 241 242 ret.putVundefined(); 243 return Dobject.RangeError(&errinfo, ERR_VALUE_OUT_OF_RANGE, 244 TEXT_toFixed, "fractionDigits"); 245 } 246 v = &othis.value; 247 x = v.toNumber(); 248 if(isNaN(x)) 249 { 250 result = TEXT_NaN; // return "NaN" 251 } 252 else 253 { 254 int sign; 255 char[] m; 256 257 sign = 0; 258 if(x < 0) 259 { 260 sign = 1; 261 x = -x; 262 } 263 if(x >= 1.0e+21) // exponent must be FIXED_DIGITS+1 264 { 265 Value vn; 266 vn.putVnumber(x); 267 ret.putVstring(vn.toString()); 268 return null; 269 } 270 else 271 { 272 number_t n; 273 tchar[32 + 1] buffer; 274 d_number tenf; 275 int f; 276 277 f = cast(int)fractionDigits; 278 tenf = tens[f]; // tenf = 10**f 279 280 // Compute n which gives |(n / tenf) - x| is the smallest 281 // value. If there are two such n's, pick the larger. 282 n = cast(number_t)(x * tenf + 0.5); // round up & chop 283 284 if(n == 0) 285 { 286 m = cast(char[])"0"; //TODO: try hacking this func to be clean ;) 287 dup = 0; 288 } 289 else 290 { 291 // n still doesn't give 20 digits, only 19 292 m = sformat(buffer[], "%d", cast(ulong)n); 293 dup = 1; 294 } 295 if(f != 0) 296 { 297 ptrdiff_t i; 298 ptrdiff_t k; 299 k = m.length; 300 if(k <= f) 301 { 302 tchar* s; 303 ptrdiff_t nzeros; 304 305 s = cast(tchar*)alloca((f + 1) * tchar.sizeof); 306 assert(s); 307 nzeros = f + 1 - k; 308 s[0 .. nzeros] = '0'; 309 s[nzeros .. f + 1] = m[0 .. k]; 310 311 m = s[0 .. f + 1]; 312 k = f + 1; 313 } 314 315 // res = "-" + m[0 .. k-f] + "." + m[k-f .. k]; 316 char[] res = new tchar[sign + k + 1]; 317 if(sign) 318 res[0] = '-'; 319 i = k - f; 320 res[sign .. sign + i] = m[0 .. i]; 321 res[sign + i] = '.'; 322 res[sign + i + 1 .. sign + k + 1] = m[i .. k]; 323 result = assumeUnique(res); 324 goto Ldone; 325 //+++ end of patch ++++ 326 } 327 } 328 if(sign) 329 result = TEXT_dash ~ m.idup; // TODO: remove idup somehow 330 else if(dup) 331 result = m.idup; 332 else 333 result = assumeUnique(m); 334 } 335 336 Ldone: 337 ret.putVstring(result); 338 return null; 339 } 340 341 /* ===================== Dnumber_prototype_toExponential =============== */ 342 343 void* Dnumber_prototype_toExponential(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 344 { 345 import std.format : format, sformat; 346 347 // ECMA v3 15.7.4.6 348 Value* varg; 349 Value* v; 350 d_number x; 351 d_number fractionDigits; 352 d_string result; 353 354 if(arglist.length) 355 { 356 varg = &arglist[0]; 357 fractionDigits = varg.toInteger(); 358 }else 359 fractionDigits = FIXED_DIGITS; 360 v = &othis.value; 361 x = v.toNumber(); 362 if(isNaN(x)) 363 { 364 result = TEXT_NaN; // return "NaN" 365 } 366 else 367 { 368 int sign; 369 370 sign = 0; 371 if(x < 0) 372 { 373 sign = 1; 374 x = -x; 375 } 376 if(std.math.isInfinity(x)) 377 { 378 result = sign ? TEXT_negInfinity : TEXT_Infinity; 379 } 380 else 381 { 382 int f; 383 number_t n; 384 int e; 385 tchar[] m; 386 int i; 387 tchar[32 + 1] buffer; 388 389 if(fractionDigits < 0 || fractionDigits > FIXED_DIGITS) 390 { 391 ErrInfo errinfo; 392 393 ret.putVundefined(); 394 return Dobject.RangeError(&errinfo, 395 ERR_VALUE_OUT_OF_RANGE, 396 TEXT_toExponential, 397 "fractionDigits"); 398 } 399 400 f = cast(int)fractionDigits; 401 if(x == 0) 402 { 403 tchar* s; 404 405 s = cast(tchar*)alloca((f + 1) * tchar.sizeof); 406 assert(s); 407 m = s[0 .. f + 1]; 408 m[0 .. f + 1] = '0'; 409 e = 0; 410 } 411 else 412 { 413 if(arglist.length && !varg.isUndefined()) 414 { 415 /* Step 12 416 * Let e and n be integers such that 417 * 10**f <= n < 10**(f+1) and for which the exact 418 * mathematical value of n * 10**(e-f) - x is as close 419 * to zero as possible. If there are two such sets of 420 * e and n, pick the e and n for which n * 10**(e-f) 421 * is larger. 422 * [Note: this is the same as Step 15 in toPrecision() 423 * with f = p - 1] 424 */ 425 n = deconstruct_real(x, f, e); 426 } 427 else 428 { 429 /* Step 19 430 * Let e, n, and f be integers such that f >= 0, 431 * 10**f <= n < 10**(f+1), the number value for 432 * n * 10**(e-f) is x, and f is as small as possible. 433 * Note that the decimal representation of n has f+1 434 * digits, n is not divisible by 10, and the least 435 * significant digit of n is not necessarilly uniquely 436 * determined by these criteria. 437 */ 438 /* Implement by trying maximum digits, and then 439 * lopping off trailing 0's. 440 */ 441 f = 19; // should use FIXED_DIGITS 442 n = deconstruct_real(x, f, e); 443 444 // Lop off trailing 0's 445 assert(n); 446 while((n % 10) == 0) 447 { 448 n /= 10; 449 f--; 450 assert(f >= 0); 451 } 452 } 453 // n still doesn't give 20 digits, only 19 454 m = sformat(buffer[], "%d", cast(ulong)n); 455 } 456 if(f) 457 { 458 tchar* s; 459 460 // m = m[0] + "." + m[1 .. f+1]; 461 s = cast(tchar*)alloca((f + 2) * tchar.sizeof); 462 assert(s); 463 s[0] = m[0]; 464 s[1] = '.'; 465 s[2 .. f + 2] = m[1 .. f + 1]; 466 m = s[0 .. f + 2]; 467 } 468 469 // result = sign + m + "e" + c + e; 470 d_string c = (e >= 0) ? "+" : ""; 471 472 result = format("%s%se%s%d", sign ? "-" : "", m, c, e); 473 } 474 } 475 476 ret.putVstring(result); 477 return null; 478 } 479 480 /* ===================== Dnumber_prototype_toPrecision =============== */ 481 482 void* Dnumber_prototype_toPrecision(Dobject pthis, CallContext *cc, Dobject othis, Value *ret, Value[] arglist) 483 { 484 import std.format : format, sformat; 485 486 // ECMA v3 15.7.4.7 487 Value* varg; 488 Value* v; 489 d_number x; 490 d_number precision; 491 d_string result; 492 493 v = &othis.value; 494 x = v.toNumber(); 495 496 varg = (arglist.length == 0) ? &vundefined : &arglist[0]; 497 498 if(arglist.length == 0 || varg.isUndefined()) 499 { 500 Value vn; 501 502 vn.putVnumber(x); 503 result = vn.toString(); 504 } 505 else 506 { 507 if(isNaN(x)) 508 result = TEXT_NaN; 509 else 510 { 511 int sign; 512 int e; 513 int p; 514 int i; 515 tchar[] m; 516 number_t n; 517 tchar[32 + 1] buffer; 518 519 sign = 0; 520 if(x < 0) 521 { 522 sign = 1; 523 x = -x; 524 } 525 526 if(std.math.isInfinity(x)) 527 { 528 result = sign ? TEXT_negInfinity : TEXT_Infinity; 529 goto Ldone; 530 } 531 532 precision = varg.toInteger(); 533 if(precision < 1 || precision > 21) 534 { 535 ErrInfo errinfo; 536 537 ret.putVundefined(); 538 return Dobject.RangeError(&errinfo, 539 ERR_VALUE_OUT_OF_RANGE, 540 TEXT_toPrecision, 541 "precision"); 542 } 543 544 p = cast(int)precision; 545 if(x != 0) 546 { 547 /* Step 15 548 * Let e and n be integers such that 10**(p-1) <= n < 10**p 549 * and for which the exact mathematical value of n * 10**(e-p+1) - x 550 * is as close to zero as possible. If there are two such sets 551 * of e and n, pick the e and n for which n * 10**(e-p+1) is larger. 552 */ 553 n = deconstruct_real(x, p - 1, e); 554 555 // n still doesn't give 20 digits, only 19 556 m = sformat(buffer[], "%d", cast(ulong)n); 557 558 if(e < -6 || e >= p) 559 { 560 // result = sign + m[0] + "." + m[1 .. p] + "e" + c + e; 561 d_string c = (e >= 0) ? "+" : ""; 562 result = format("%s%s.%se%s%d", 563 (sign ? "-" : ""), m[0], m[1 .. $], c, e); 564 goto Ldone; 565 } 566 } 567 else 568 { 569 // Step 12 570 // m = array[p] of '0' 571 tchar* s; 572 s = cast(tchar*)alloca(p * tchar.sizeof); 573 assert(s); 574 m = s[0 .. p]; 575 m[] = '0'; 576 577 e = 0; 578 } 579 if(e != p - 1) 580 { 581 tchar* s; 582 583 if(e >= 0) 584 { 585 // m = m[0 .. e+1] + "." + m[e+1 .. p]; 586 587 s = cast(tchar*)alloca((p + 1) * tchar.sizeof); 588 assert(s); 589 i = e + 1; 590 s[0 .. i] = m[0 .. i]; 591 s[i] = '.'; 592 s[i + 1 .. p + 1] = m[i .. p]; 593 m = s[0 .. p + 1]; 594 } 595 else 596 { 597 // m = "0." + (-(e+1) occurrences of the character '0') + m; 598 int imax = 2 + - (e + 1); 599 600 s = cast(tchar*)alloca((imax + p) * tchar.sizeof); 601 assert(s); 602 s[0] = '0'; 603 s[1] = '.'; 604 s[2 .. imax] = '0'; 605 s[imax .. imax + p] = m[0 .. p]; 606 m = s[0 .. imax + p]; 607 } 608 } 609 if(sign) 610 result = TEXT_dash ~ m.idup; //TODO: remove idup somehow 611 else 612 result = m.idup; 613 } 614 } 615 616 Ldone: 617 ret.putVstring(result); 618 return null; 619 } 620 621 /* ===================== Dnumber_prototype ==================== */ 622 623 class DnumberPrototype : Dnumber 624 { 625 this() 626 { 627 super(Dobject_prototype); 628 uint attributes = DontEnum; 629 630 Dobject f = Dfunction_prototype; 631 632 Put(TEXT_constructor, Dnumber_constructor, attributes); 633 634 static enum NativeFunctionData[] nfd = 635 [ 636 { TEXT_toString, &Dnumber_prototype_toString, 1 }, 637 // Permissible to use toString() 638 { TEXT_toLocaleString, &Dnumber_prototype_toLocaleString, 1 }, 639 { TEXT_valueOf, &Dnumber_prototype_valueOf, 0 }, 640 { TEXT_toFixed, &Dnumber_prototype_toFixed, 1 }, 641 { TEXT_toExponential, &Dnumber_prototype_toExponential, 1 }, 642 { TEXT_toPrecision, &Dnumber_prototype_toPrecision, 1 }, 643 ]; 644 645 DnativeFunction.initialize(this, nfd, attributes); 646 } 647 } 648 649 650 /* ===================== Dnumber ==================== */ 651 652 class Dnumber : Dobject 653 { 654 this(d_number n) 655 { 656 super(getPrototype()); 657 classname = TEXT_Number; 658 value.putVnumber(n); 659 } 660 661 this(Dobject prototype) 662 { 663 super(prototype); 664 classname = TEXT_Number; 665 value.putVnumber(0); 666 } 667 668 static Dfunction getConstructor() 669 { 670 return Dnumber_constructor; 671 } 672 673 static Dobject getPrototype() 674 { 675 return Dnumber_prototype; 676 } 677 678 static void initialize() 679 { 680 Dnumber_constructor = new DnumberConstructor(); 681 Dnumber_prototype = new DnumberPrototype(); 682 683 Dnumber_constructor.Put(TEXT_prototype, Dnumber_prototype, DontEnum | DontDelete | ReadOnly); 684 } 685 } 686