xref: /reactos/dll/win32/jscript/number.c (revision c2c66aff)
1 /*
2  * Copyright 2008 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "jscript.h"
20 
21 typedef struct {
22     jsdisp_t dispex;
23 
24     double value;
25 } NumberInstance;
26 
27 static const WCHAR toStringW[] = {'t','o','S','t','r','i','n','g',0};
28 static const WCHAR toLocaleStringW[] = {'t','o','L','o','c','a','l','e','S','t','r','i','n','g',0};
29 static const WCHAR toFixedW[] = {'t','o','F','i','x','e','d',0};
30 static const WCHAR toExponentialW[] = {'t','o','E','x','p','o','n','e','n','t','i','a','l',0};
31 static const WCHAR toPrecisionW[] = {'t','o','P','r','e','c','i','s','i','o','n',0};
32 static const WCHAR valueOfW[] = {'v','a','l','u','e','O','f',0};
33 
34 #define NUMBER_TOSTRING_BUF_SIZE 64
35 #define NUMBER_DTOA_SIZE 18
36 
37 static inline NumberInstance *number_from_jsdisp(jsdisp_t *jsdisp)
38 {
39     return CONTAINING_RECORD(jsdisp, NumberInstance, dispex);
40 }
41 
42 static inline NumberInstance *number_from_vdisp(vdisp_t *vdisp)
43 {
44     return number_from_jsdisp(vdisp->u.jsdisp);
45 }
46 
47 static inline NumberInstance *number_this(vdisp_t *jsthis)
48 {
49     return is_vclass(jsthis, JSCLASS_NUMBER) ? number_from_vdisp(jsthis) : NULL;
50 }
51 
52 static inline void number_to_str(double d, WCHAR *buf, int size, int *dec_point)
53 {
54     ULONGLONG l;
55     int i;
56 
57     /* TODO: this function should print doubles with bigger precision */
58     assert(size>=2 && size<=NUMBER_DTOA_SIZE && d>=0);
59 
60     if(d == 0)
61         *dec_point = 0;
62     else
63         *dec_point = floor(log10(d));
64     l = d*pow(10, size-*dec_point-1);
65 
66     if(l%10 >= 5)
67         l = l/10+1;
68     else
69         l /= 10;
70 
71     buf[size-1] = 0;
72     for(i=size-2; i>=0; i--) {
73         buf[i] = '0'+l%10;
74         l /= 10;
75     }
76 
77     /* log10 was wrong by 1 or rounding changed number of digits */
78     if(l) {
79         (*dec_point)++;
80         memmove(buf+1, buf, size-2);
81         buf[0] = '0'+l;
82     }else if(buf[0]=='0' && buf[1]>='1' && buf[1]<='9') {
83         (*dec_point)--;
84         memmove(buf, buf+1, size-2);
85         buf[size-2] = '0';
86     }
87 }
88 
89 static inline jsstr_t *number_to_fixed(double val, int prec)
90 {
91     WCHAR buf[NUMBER_DTOA_SIZE];
92     int dec_point, size, buf_size, buf_pos;
93     BOOL neg = FALSE;
94     jsstr_t *ret;
95     WCHAR *str;
96 
97     TRACE("%lf %d\n", val, prec);
98 
99     if(val < 0) {
100         neg = TRUE;
101         val = -val;
102     }
103 
104     if(val >= 1)
105         buf_size = log10(val)+prec+2;
106     else
107         buf_size = prec ? prec+1 : 2;
108     if(buf_size > NUMBER_DTOA_SIZE)
109         buf_size = NUMBER_DTOA_SIZE;
110 
111     number_to_str(val, buf, buf_size, &dec_point);
112     dec_point++;
113     size = 0;
114     if(neg)
115         size++;
116     if(dec_point > 0)
117         size += dec_point;
118     else
119         size++;
120     if(prec)
121         size += prec+1;
122 
123     ret = jsstr_alloc_buf(size, &str);
124     if(!ret)
125         return NULL;
126 
127     size = buf_pos = 0;
128     if(neg)
129         str[size++] = '-';
130     if(dec_point > 0) {
131         for(;buf_pos<buf_size-1 && dec_point; dec_point--)
132             str[size++] = buf[buf_pos++];
133     }else {
134         str[size++] = '0';
135     }
136     for(; dec_point>0; dec_point--)
137         str[size++] = '0';
138     if(prec) {
139         str[size++] = '.';
140 
141         for(; dec_point<0 && prec; dec_point++, prec--)
142             str[size++] = '0';
143         for(; buf_pos<buf_size-1 && prec; prec--)
144             str[size++] = buf[buf_pos++];
145         for(; prec; prec--) {
146             str[size++] = '0';
147         }
148     }
149     str[size++] = 0;
150     return ret;
151 }
152 
153 static inline jsstr_t *number_to_exponential(double val, int prec)
154 {
155     WCHAR buf[NUMBER_DTOA_SIZE], *pbuf;
156     int dec_point, size, buf_size, exp_size = 1;
157     BOOL neg = FALSE;
158     jsstr_t *ret;
159     WCHAR *str;
160 
161     if(val < 0) {
162         neg = TRUE;
163         val = -val;
164     }
165 
166     buf_size = prec+2;
167     if(buf_size<2 || buf_size>NUMBER_DTOA_SIZE)
168         buf_size = NUMBER_DTOA_SIZE;
169     number_to_str(val, buf, buf_size, &dec_point);
170     buf_size--;
171     if(prec == -1)
172         for(; buf_size>1 && buf[buf_size-1]=='0'; buf_size--)
173             buf[buf_size-1] = 0;
174 
175     size = 10;
176     while(dec_point>=size || dec_point<=-size) {
177         size *= 10;
178         exp_size++;
179     }
180 
181     if(buf_size == 1)
182         size = buf_size+2+exp_size; /* 2 = strlen(e+) */
183     else if(prec == -1)
184         size = buf_size+3+exp_size; /* 3 = strlen(.e+) */
185     else
186         size = prec+4+exp_size; /* 4 = strlen(0.e+) */
187     if(neg)
188         size++;
189 
190     ret = jsstr_alloc_buf(size, &str);
191     if(!ret)
192         return NULL;
193 
194     size = 0;
195     pbuf = buf;
196     if(neg)
197         str[size++] = '-';
198     str[size++] = *pbuf++;
199     if(buf_size != 1) {
200         str[size++] = '.';
201         while(*pbuf)
202             str[size++] = *pbuf++;
203         for(; prec>buf_size-1; prec--)
204             str[size++] = '0';
205     }
206     str[size++] = 'e';
207     if(dec_point >= 0) {
208         str[size++] = '+';
209     }else {
210         str[size++] = '-';
211         dec_point = -dec_point;
212     }
213     size += exp_size;
214     do {
215         str[--size] = '0'+dec_point%10;
216         dec_point /= 10;
217     }while(dec_point>0);
218     size += exp_size;
219     str[size] = 0;
220 
221     return ret;
222 }
223 
224 /* ECMA-262 3rd Edition    15.7.4.2 */
225 static HRESULT Number_toString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
226         jsval_t *r)
227 {
228     NumberInstance *number;
229     INT radix = 10;
230     DOUBLE val;
231     jsstr_t *str;
232     HRESULT hres;
233 
234     TRACE("\n");
235 
236     if(!(number = number_this(jsthis)))
237         return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL);
238 
239     if(argc) {
240         hres = to_int32(ctx, argv[0], &radix);
241         if(FAILED(hres))
242             return hres;
243 
244         if(radix<2 || radix>36)
245             return throw_type_error(ctx, JS_E_INVALIDARG, NULL);
246     }
247 
248     val = number->value;
249 
250     if(radix==10 || !is_finite(val)) {
251         hres = to_string(ctx, jsval_number(val), &str);
252         if(FAILED(hres))
253             return hres;
254     }else {
255         INT idx = 0;
256         DOUBLE integ, frac, log_radix = 0;
257         WCHAR buf[NUMBER_TOSTRING_BUF_SIZE+16];
258         BOOL exp = FALSE;
259 
260         if(val<0) {
261             val = -val;
262             buf[idx++] = '-';
263         }
264 
265         while(1) {
266             integ = floor(val);
267             frac = val-integ;
268 
269             if(integ == 0)
270                 buf[idx++] = '0';
271             while(integ>=1 && idx<NUMBER_TOSTRING_BUF_SIZE) {
272                 buf[idx] = fmod(integ, radix);
273                 if(buf[idx]<10) buf[idx] += '0';
274                 else buf[idx] += 'a'-10;
275                 integ /= radix;
276                 idx++;
277             }
278 
279             if(idx<NUMBER_TOSTRING_BUF_SIZE) {
280                 INT beg = buf[0]=='-'?1:0;
281                 INT end = idx-1;
282                 WCHAR wch;
283 
284                 while(end > beg) {
285                     wch = buf[beg];
286                     buf[beg++] = buf[end];
287                     buf[end--] = wch;
288                 }
289             }
290 
291             if(idx != NUMBER_TOSTRING_BUF_SIZE) buf[idx++] = '.';
292 
293             while(frac>0 && idx<NUMBER_TOSTRING_BUF_SIZE) {
294                 frac *= radix;
295                 buf[idx] = fmod(frac, radix);
296                 frac -= buf[idx];
297                 if(buf[idx]<10) buf[idx] += '0';
298                 else buf[idx] += 'a'-10;
299                 idx++;
300             }
301 
302             if(idx==NUMBER_TOSTRING_BUF_SIZE && !exp) {
303                 exp = TRUE;
304                 idx = (buf[0]=='-') ? 1 : 0;
305                 log_radix = floor(log(val)/log(radix));
306                 val *= pow(radix, -log_radix);
307                 continue;
308             }
309 
310             break;
311         }
312 
313         while(buf[idx-1] == '0') idx--;
314         if(buf[idx-1] == '.') idx--;
315 
316         if(exp) {
317             if(log_radix==0)
318                 buf[idx] = 0;
319             else {
320                 static const WCHAR formatW[] = {'(','e','%','c','%','d',')',0};
321                 WCHAR ch;
322 
323                 if(log_radix<0) {
324                     log_radix = -log_radix;
325                     ch = '-';
326                 }
327                 else ch = '+';
328                 sprintfW(&buf[idx], formatW, ch, (int)log_radix);
329             }
330         }
331         else buf[idx] = '\0';
332 
333         str = jsstr_alloc(buf);
334         if(!str)
335             return E_OUTOFMEMORY;
336     }
337 
338     if(r)
339         *r = jsval_string(str);
340     else
341         jsstr_release(str);
342     return S_OK;
343 }
344 
345 static HRESULT Number_toLocaleString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
346         jsval_t *r)
347 {
348     FIXME("\n");
349     return E_NOTIMPL;
350 }
351 
352 static HRESULT Number_toFixed(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
353         jsval_t *r)
354 {
355     NumberInstance *number;
356     DOUBLE val;
357     INT prec = 0;
358     jsstr_t *str;
359     HRESULT hres;
360 
361     TRACE("\n");
362 
363     if(!(number = number_this(jsthis)))
364         return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL);
365 
366     if(argc) {
367         hres = to_int32(ctx, argv[0], &prec);
368         if(FAILED(hres))
369             return hres;
370 
371         if(prec<0 || prec>20)
372             return throw_range_error(ctx, JS_E_FRACTION_DIGITS_OUT_OF_RANGE, NULL);
373     }
374 
375     val = number->value;
376     if(!is_finite(val)) {
377         hres = to_string(ctx, jsval_number(val), &str);
378         if(FAILED(hres))
379             return hres;
380     }else {
381         str = number_to_fixed(val, prec);
382         if(!str)
383             return E_OUTOFMEMORY;
384     }
385 
386     if(r)
387         *r = jsval_string(str);
388     else
389         jsstr_release(str);
390     return S_OK;
391 }
392 
393 static HRESULT Number_toExponential(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
394         jsval_t *r)
395 {
396     NumberInstance *number;
397     DOUBLE val;
398     INT prec = 0;
399     jsstr_t *str;
400     HRESULT hres;
401 
402     TRACE("\n");
403 
404     if(!(number = number_this(jsthis)))
405         return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL);
406 
407     if(argc) {
408         hres = to_int32(ctx, argv[0], &prec);
409         if(FAILED(hres))
410             return hres;
411 
412         if(prec<0 || prec>20)
413             return throw_range_error(ctx, JS_E_FRACTION_DIGITS_OUT_OF_RANGE, NULL);
414     }
415 
416     val = number->value;
417     if(!is_finite(val)) {
418         hres = to_string(ctx, jsval_number(val), &str);
419         if(FAILED(hres))
420             return hres;
421     }else {
422         if(!prec)
423             prec--;
424         str = number_to_exponential(val, prec);
425         if(!str)
426             return E_OUTOFMEMORY;
427     }
428 
429     if(r)
430         *r = jsval_string(str);
431     else
432         jsstr_release(str);
433     return S_OK;
434 }
435 
436 static HRESULT Number_toPrecision(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
437         jsval_t *r)
438 {
439     NumberInstance *number;
440     INT prec = 0, size;
441     jsstr_t *str;
442     DOUBLE val;
443     HRESULT hres;
444 
445     if(!(number = number_this(jsthis)))
446         return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL);
447 
448     if(argc) {
449         hres = to_int32(ctx, argv[0], &prec);
450         if(FAILED(hres))
451             return hres;
452 
453         if(prec<1 || prec>21)
454             return throw_range_error(ctx, JS_E_PRECISION_OUT_OF_RANGE, NULL);
455     }
456 
457     val = number->value;
458     if(!is_finite(val) || !prec) {
459         hres = to_string(ctx, jsval_number(val), &str);
460         if(FAILED(hres))
461             return hres;
462     }else {
463         if(val != 0)
464             size = floor(log10(val>0 ? val : -val)) + 1;
465         else
466             size = 1;
467 
468         if(size > prec)
469             str = number_to_exponential(val, prec-1);
470         else
471             str = number_to_fixed(val, prec-size);
472         if(!str)
473             return E_OUTOFMEMORY;
474     }
475 
476     if(r)
477         *r = jsval_string(str);
478     else
479         jsstr_release(str);
480     return S_OK;
481 }
482 
483 static HRESULT Number_valueOf(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
484         jsval_t *r)
485 {
486     NumberInstance *number;
487 
488     TRACE("\n");
489 
490     if(!(number = number_this(jsthis)))
491         return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL);
492 
493     if(r)
494         *r = jsval_number(number->value);
495     return S_OK;
496 }
497 
498 static HRESULT Number_get_value(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r)
499 {
500     NumberInstance *number = number_from_jsdisp(jsthis);
501 
502     TRACE("(%p)\n", number);
503 
504     *r = jsval_number(number->value);
505     return S_OK;
506 }
507 
508 static const builtin_prop_t Number_props[] = {
509     {toExponentialW,         Number_toExponential,         PROPF_METHOD|1},
510     {toFixedW,               Number_toFixed,               PROPF_METHOD},
511     {toLocaleStringW,        Number_toLocaleString,        PROPF_METHOD},
512     {toPrecisionW,           Number_toPrecision,           PROPF_METHOD|1},
513     {toStringW,              Number_toString,              PROPF_METHOD|1},
514     {valueOfW,               Number_valueOf,               PROPF_METHOD}
515 };
516 
517 static const builtin_info_t Number_info = {
518     JSCLASS_NUMBER,
519     {NULL, NULL,0, Number_get_value},
520     sizeof(Number_props)/sizeof(*Number_props),
521     Number_props,
522     NULL,
523     NULL
524 };
525 
526 static const builtin_info_t NumberInst_info = {
527     JSCLASS_NUMBER,
528     {NULL, NULL,0, Number_get_value},
529     0, NULL,
530     NULL,
531     NULL
532 };
533 
534 static HRESULT NumberConstr_value(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
535         jsval_t *r)
536 {
537     double n;
538     HRESULT hres;
539 
540     TRACE("\n");
541 
542     switch(flags) {
543     case INVOKE_FUNC:
544         if(!argc) {
545             if(r)
546                 *r = jsval_number(0);
547             return S_OK;
548         }
549 
550         hres = to_number(ctx, argv[0], &n);
551         if(FAILED(hres))
552             return hres;
553 
554         if(r)
555             *r = jsval_number(n);
556         break;
557 
558     case DISPATCH_CONSTRUCT: {
559         jsdisp_t *obj;
560 
561         if(argc) {
562             hres = to_number(ctx, argv[0], &n);
563             if(FAILED(hres))
564                 return hres;
565         }else {
566             n = 0;
567         }
568 
569         hres = create_number(ctx, n, &obj);
570         if(FAILED(hres))
571             return hres;
572 
573         *r = jsval_obj(obj);
574         break;
575     }
576     default:
577         FIXME("unimplemented flags %x\n", flags);
578         return E_NOTIMPL;
579     }
580 
581     return S_OK;
582 }
583 
584 static HRESULT alloc_number(script_ctx_t *ctx, jsdisp_t *object_prototype, NumberInstance **ret)
585 {
586     NumberInstance *number;
587     HRESULT hres;
588 
589     number = heap_alloc_zero(sizeof(NumberInstance));
590     if(!number)
591         return E_OUTOFMEMORY;
592 
593     if(object_prototype)
594         hres = init_dispex(&number->dispex, ctx, &Number_info, object_prototype);
595     else
596         hres = init_dispex_from_constr(&number->dispex, ctx, &NumberInst_info, ctx->number_constr);
597     if(FAILED(hres)) {
598         heap_free(number);
599         return hres;
600     }
601 
602     *ret = number;
603     return S_OK;
604 }
605 
606 HRESULT create_number_constr(script_ctx_t *ctx, jsdisp_t *object_prototype, jsdisp_t **ret)
607 {
608     NumberInstance *number;
609     HRESULT hres;
610 
611     static const WCHAR NumberW[] = {'N','u','m','b','e','r',0};
612 
613     hres = alloc_number(ctx, object_prototype, &number);
614     if(FAILED(hres))
615         return hres;
616 
617     number->value = 0;
618     hres = create_builtin_constructor(ctx, NumberConstr_value, NumberW, NULL,
619             PROPF_CONSTR|1, &number->dispex, ret);
620 
621     jsdisp_release(&number->dispex);
622     return hres;
623 }
624 
625 HRESULT create_number(script_ctx_t *ctx, double value, jsdisp_t **ret)
626 {
627     NumberInstance *number;
628     HRESULT hres;
629 
630     hres = alloc_number(ctx, NULL, &number);
631     if(FAILED(hres))
632         return hres;
633 
634     number->value = value;
635 
636     *ret = &number->dispex;
637     return S_OK;
638 }
639