cebcc9f3ef76826532e101e72161c2f07f238df2
[reactos.git] / reactos / dll / win32 / jscript / number.c
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 str = jsstr_alloc_buf(size, &ret);
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 str = jsstr_alloc_buf(size, &ret);
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 || isnan(val) || isinf(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(isinf(val) || isnan(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(isinf(val) || isnan(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(isinf(val) || isnan(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 }