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