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