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