2 * Copyright 2008 Jacek Caban for CodeWeavers
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.
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.
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
25 #include "wine/debug.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(jscript
);
35 #define NUMBER_TOSTRING_BUF_SIZE 64
36 #define NUMBER_DTOA_SIZE 18
38 static inline NumberInstance
*number_from_jsdisp(jsdisp_t
*jsdisp
)
40 return CONTAINING_RECORD(jsdisp
, NumberInstance
, dispex
);
43 static inline HRESULT
numberval_this(jsval_t vthis
, DOUBLE
*ret
)
47 *ret
= get_number(vthis
);
48 else if(is_object_instance(vthis
) && (jsdisp
= to_jsdisp(get_object(vthis
))) && is_class(jsdisp
, JSCLASS_NUMBER
))
49 *ret
= number_from_jsdisp(jsdisp
)->value
;
51 return JS_E_NUMBER_EXPECTED
;
55 static inline void number_to_str(double d
, WCHAR
*buf
, int size
, int *dec_point
)
60 /* TODO: this function should print doubles with bigger precision */
61 assert(size
>=2 && size
<=NUMBER_DTOA_SIZE
&& d
>=0);
66 *dec_point
= floor(log10(d
));
67 l
= d
*pow(10, size
-*dec_point
-1);
75 for(i
=size
-2; i
>=0; i
--) {
80 /* log10 was wrong by 1 or rounding changed number of digits */
83 memmove(buf
+1, buf
, size
-2);
85 }else if(buf
[0]=='0' && buf
[1]>='1' && buf
[1]<='9') {
87 memmove(buf
, buf
+1, size
-2);
92 static inline jsstr_t
*number_to_fixed(double val
, int prec
)
94 WCHAR buf
[NUMBER_DTOA_SIZE
];
95 int dec_point
, size
, buf_size
, buf_pos
;
100 TRACE("%lf %d\n", val
, prec
);
108 buf_size
= log10(val
)+prec
+2;
110 buf_size
= prec
? prec
+1 : 2;
111 if(buf_size
> NUMBER_DTOA_SIZE
)
112 buf_size
= NUMBER_DTOA_SIZE
;
114 number_to_str(val
, buf
, buf_size
, &dec_point
);
126 ret
= jsstr_alloc_buf(size
, &str
);
134 for(;buf_pos
<buf_size
-1 && dec_point
; dec_point
--)
135 str
[size
++] = buf
[buf_pos
++];
139 for(; dec_point
>0; dec_point
--)
144 for(; dec_point
<0 && prec
; dec_point
++, prec
--)
146 for(; buf_pos
<buf_size
-1 && prec
; prec
--)
147 str
[size
++] = buf
[buf_pos
++];
148 for(; prec
; prec
--) {
156 static inline jsstr_t
*number_to_exponential(double val
, int prec
)
158 WCHAR buf
[NUMBER_DTOA_SIZE
], *pbuf
;
159 int dec_point
, size
, buf_size
, exp_size
= 1;
170 if(buf_size
<2 || buf_size
>NUMBER_DTOA_SIZE
)
171 buf_size
= NUMBER_DTOA_SIZE
;
172 number_to_str(val
, buf
, buf_size
, &dec_point
);
175 for(; buf_size
>1 && buf
[buf_size
-1]=='0'; buf_size
--)
179 while(dec_point
>=size
|| dec_point
<=-size
) {
185 size
= buf_size
+2+exp_size
; /* 2 = strlen(e+) */
187 size
= buf_size
+3+exp_size
; /* 3 = strlen(.e+) */
189 size
= prec
+4+exp_size
; /* 4 = strlen(0.e+) */
193 ret
= jsstr_alloc_buf(size
, &str
);
201 str
[size
++] = *pbuf
++;
205 str
[size
++] = *pbuf
++;
206 for(; prec
>buf_size
-1; prec
--)
214 dec_point
= -dec_point
;
218 str
[--size
] = '0'+dec_point
%10;
227 /* ECMA-262 3rd Edition 15.7.4.2 */
228 static HRESULT
Number_toString(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
238 hres
= numberval_this(vthis
, &val
);
242 if(argc
&& (ctx
->version
< SCRIPTLANGUAGEVERSION_ES5
|| !is_undefined(argv
[0]))) {
243 hres
= to_int32(ctx
, argv
[0], &radix
);
247 if(radix
< 2 || radix
> 36)
248 return JS_E_INVALIDARG
;
251 if(radix
==10 || !isfinite(val
)) {
252 hres
= to_string(ctx
, jsval_number(val
), &str
);
257 DOUBLE integ
, frac
, log_radix
= 0;
258 WCHAR buf
[NUMBER_TOSTRING_BUF_SIZE
+16];
272 while(integ
>=1 && idx
<NUMBER_TOSTRING_BUF_SIZE
) {
273 buf
[idx
] = fmod(integ
, radix
);
274 if(buf
[idx
]<10) buf
[idx
] += '0';
275 else buf
[idx
] += 'a'-10;
280 if(idx
<NUMBER_TOSTRING_BUF_SIZE
) {
281 INT beg
= buf
[0]=='-'?1:0;
287 buf
[beg
++] = buf
[end
];
292 if(idx
!= NUMBER_TOSTRING_BUF_SIZE
) buf
[idx
++] = '.';
294 while(frac
>0 && idx
<NUMBER_TOSTRING_BUF_SIZE
) {
296 buf
[idx
] = fmod(frac
, radix
);
298 if(buf
[idx
]<10) buf
[idx
] += '0';
299 else buf
[idx
] += 'a'-10;
303 if(idx
==NUMBER_TOSTRING_BUF_SIZE
&& !exp
) {
305 idx
= (buf
[0]=='-') ? 1 : 0;
306 log_radix
= floor(log(val
)/log(radix
));
307 val
*= pow(radix
, -log_radix
);
314 while(buf
[idx
-1] == '0') idx
--;
315 if(buf
[idx
-1] == '.') idx
--;
324 log_radix
= -log_radix
;
328 swprintf(&buf
[idx
], ARRAY_SIZE(buf
) - idx
, L
"(e%c%d)", ch
, (int)log_radix
);
331 else buf
[idx
] = '\0';
333 str
= jsstr_alloc(buf
);
335 return E_OUTOFMEMORY
;
339 *r
= jsval_string(str
);
345 HRESULT
localize_number(script_ctx_t
*ctx
, DOUBLE val
, BOOL new_format
, jsstr_t
**ret
)
347 WCHAR buf
[316], decimal
[8], thousands
[8], *numstr
;
348 NUMBERFMTW
*format
= NULL
, format_buf
;
349 LCID lcid
= ctx
->lcid
;
355 /* FIXME: Localize this */
357 return to_string(ctx
, jsval_number(val
), ret
);
359 /* Native never uses an exponent, even if the number is very large, it will in fact
360 return all the digits (with thousands separators). jscript.dll uses two digits for
361 fraction even if they are zero (likely default numDigits) and always returns them,
362 while mshtml's jscript uses 3 digits and trims trailing zeros (on same locale).
363 This is even for very small numbers, such as 0.0000999, which will simply be 0. */
364 if(!(locale
= _create_locale(LC_ALL
, "C")))
365 return E_OUTOFMEMORY
;
366 len
= _swprintf_l(buf
, ARRAY_SIZE(buf
), L
"%.3f", locale
, val
);
367 _free_locale(locale
);
372 format
= &format_buf
;
373 format
->NumDigits
= 3;
374 while(buf
[--len
] == '0')
377 /* same logic as VarFormatNumber */
379 if(!GetLocaleInfoW(lcid
, LOCALE_SGROUPING
, grouping
, ARRAY_SIZE(grouping
)))
380 format
->Grouping
= 3;
382 format
->Grouping
= (grouping
[2] == '2' ? 32 : grouping
[0] - '0');
384 if(!GetLocaleInfoW(lcid
, LOCALE_ILZERO
| LOCALE_RETURN_NUMBER
, (WCHAR
*)&format
->LeadingZero
, 2))
385 format
->LeadingZero
= 0;
386 if(!GetLocaleInfoW(lcid
, LOCALE_INEGNUMBER
| LOCALE_RETURN_NUMBER
, (WCHAR
*)&format
->NegativeOrder
, 2))
387 format
->NegativeOrder
= 1;
388 format
->lpDecimalSep
= decimal
;
389 if(!GetLocaleInfoW(lcid
, LOCALE_SDECIMAL
, decimal
, ARRAY_SIZE(decimal
)))
390 wcscpy(decimal
, L
".");
391 format
->lpThousandSep
= thousands
;
392 if(!GetLocaleInfoW(lcid
, LOCALE_STHOUSAND
, thousands
, ARRAY_SIZE(thousands
)))
393 wcscpy(thousands
, L
",");
396 if(!(convlen
= GetNumberFormatW(lcid
, 0, buf
, format
, NULL
, 0)) ||
397 !(str
= jsstr_alloc_buf(convlen
- 1, &numstr
)))
398 return E_OUTOFMEMORY
;
400 if(!GetNumberFormatW(lcid
, 0, buf
, format
, numstr
, convlen
)) {
402 return E_OUTOFMEMORY
;
409 static HRESULT
Number_toLocaleString(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
418 hres
= numberval_this(vthis
, &val
);
420 if(hres
== JS_E_NUMBER_EXPECTED
&& ctx
->version
>= SCRIPTLANGUAGEVERSION_ES5
)
421 return throw_error(ctx
, JS_E_WRONG_THIS
, L
"Number");
426 hres
= localize_number(ctx
, val
, ctx
->version
>= SCRIPTLANGUAGEVERSION_ES5
, &str
);
429 *r
= jsval_string(str
);
434 static HRESULT
Number_toFixed(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
444 hres
= numberval_this(vthis
, &val
);
449 hres
= to_int32(ctx
, argv
[0], &prec
);
453 if(prec
< 0 || prec
> 20)
454 return JS_E_FRACTION_DIGITS_OUT_OF_RANGE
;
458 hres
= to_string(ctx
, jsval_number(val
), &str
);
462 str
= number_to_fixed(val
, prec
);
464 return E_OUTOFMEMORY
;
468 *r
= jsval_string(str
);
474 static HRESULT
Number_toExponential(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
484 hres
= numberval_this(vthis
, &val
);
489 hres
= to_int32(ctx
, argv
[0], &prec
);
493 if(prec
< 0 || prec
> 20)
494 return JS_E_FRACTION_DIGITS_OUT_OF_RANGE
;
498 hres
= to_string(ctx
, jsval_number(val
), &str
);
504 str
= number_to_exponential(val
, prec
);
506 return E_OUTOFMEMORY
;
510 *r
= jsval_string(str
);
516 static HRESULT
Number_toPrecision(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
524 hres
= numberval_this(vthis
, &val
);
528 if(argc
&& (ctx
->version
< 2 || !is_undefined(argv
[0]))) {
529 hres
= to_int32(ctx
, argv
[0], &prec
);
533 if(prec
<1 || prec
>21)
534 return JS_E_PRECISION_OUT_OF_RANGE
;
537 if(!isfinite(val
) || !prec
) {
538 hres
= to_string(ctx
, jsval_number(val
), &str
);
543 size
= floor(log10(val
>0 ? val
: -val
)) + 1;
548 str
= number_to_exponential(val
, prec
-1);
550 str
= number_to_fixed(val
, prec
-size
);
552 return E_OUTOFMEMORY
;
556 *r
= jsval_string(str
);
562 static HRESULT
Number_valueOf(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
570 hres
= numberval_this(vthis
, &val
);
575 *r
= jsval_number(val
);
579 static const builtin_prop_t Number_props
[] = {
580 {L
"toExponential", Number_toExponential
, PROPF_METHOD
|1},
581 {L
"toFixed", Number_toFixed
, PROPF_METHOD
},
582 {L
"toLocaleString", Number_toLocaleString
, PROPF_METHOD
},
583 {L
"toPrecision", Number_toPrecision
, PROPF_METHOD
|1},
584 {L
"toString", Number_toString
, PROPF_METHOD
|1},
585 {L
"valueOf", Number_valueOf
, PROPF_METHOD
}
588 static const builtin_info_t Number_info
= {
589 .class = JSCLASS_NUMBER
,
590 .props_cnt
= ARRAY_SIZE(Number_props
),
591 .props
= Number_props
,
594 static const builtin_info_t NumberInst_info
= {
595 .class = JSCLASS_NUMBER
,
598 static HRESULT
NumberConstr_value(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
610 *r
= jsval_number(0);
614 hres
= to_number(ctx
, argv
[0], &n
);
619 *r
= jsval_number(n
);
622 case DISPATCH_CONSTRUCT
: {
626 hres
= to_number(ctx
, argv
[0], &n
);
634 hres
= create_number(ctx
, n
, &obj
);
642 FIXME("unimplemented flags %x\n", flags
);
649 static HRESULT
alloc_number(script_ctx_t
*ctx
, jsdisp_t
*object_prototype
, NumberInstance
**ret
)
651 NumberInstance
*number
;
654 number
= calloc(1, sizeof(NumberInstance
));
656 return E_OUTOFMEMORY
;
659 hres
= init_dispex(&number
->dispex
, ctx
, &Number_info
, object_prototype
);
661 hres
= init_dispex_from_constr(&number
->dispex
, ctx
, &NumberInst_info
, ctx
->number_constr
);
671 HRESULT
create_number_constr(script_ctx_t
*ctx
, jsdisp_t
*object_prototype
, jsdisp_t
**ret
)
673 NumberInstance
*number
;
676 hres
= alloc_number(ctx
, object_prototype
, &number
);
681 hres
= create_builtin_constructor(ctx
, NumberConstr_value
, L
"Number", NULL
,
682 PROPF_CONSTR
|1, &number
->dispex
, ret
);
684 jsdisp_release(&number
->dispex
);
688 HRESULT
create_number(script_ctx_t
*ctx
, double value
, jsdisp_t
**ret
)
690 NumberInstance
*number
;
693 hres
= alloc_number(ctx
, NULL
, &number
);
697 number
->value
= value
;
699 *ret
= &number
->dispex
;