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