MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

use double-conversion for JS number parsing

Add a shared number conversion layer backed by double-conversion and route
Number(), parseFloat(), numeric literal parsing, and Number formatting through
it. This fixes decimal precision/rounding edge cases, validates malformed
decimal literals such as missing exponent digits, and handles JS string-number
parsing rules more consistently.

+619 -367
+4 -4
examples/results.txt
··· 931 931 compat-table/es6/let.strict.shadow.js: OK 932 932 compat-table/es6/let.strict.tdz.js: OK 933 933 compat-table/es6/let.tdz.js: OK 934 - compat-table/es6/literals.binary.Number.js: failed 934 + compat-table/es6/literals.binary.Number.js: OK 935 935 compat-table/es6/literals.binary.js: OK 936 936 compat-table/es6/literals.object.computed-accessors.js: OK 937 937 compat-table/es6/literals.object.computed-properties.js: OK ··· 939 939 compat-table/es6/literals.object.shorthand-methods.js: OK 940 940 compat-table/es6/literals.object.shorthand-properties.js: OK 941 941 compat-table/es6/literals.object.string-keyed-shorthand-methods.js: OK 942 - compat-table/es6/literals.octal.Number.js: failed 942 + compat-table/es6/literals.octal.Number.js: OK 943 943 compat-table/es6/literals.octal.js: OK 944 944 compat-table/es6/misc.Invalid-Date.js: OK 945 945 compat-table/es6/misc.Object.freeze.primitives.js: OK ··· 1115 1115 compat-table/es6/template.tagged.js: OK 1116 1116 compat-table/es6/template.toString.js: OK 1117 1117 compat-table/es6/typed-arrays.ArrayBuffer.Symbol.species.js: OK 1118 - compat-table/es6/typed-arrays.DataView.Float32.js: failed 1118 + compat-table/es6/typed-arrays.DataView.Float32.js: OK 1119 1119 compat-table/es6/typed-arrays.DataView.Float64.js: OK 1120 1120 compat-table/es6/typed-arrays.DataView.Int8.js: OK 1121 1121 compat-table/es6/typed-arrays.DataView.Int16.js: OK ··· 1123 1123 compat-table/es6/typed-arrays.DataView.Uint8.js: OK 1124 1124 compat-table/es6/typed-arrays.DataView.Uint16.js: OK 1125 1125 compat-table/es6/typed-arrays.DataView.Uint32.js: OK 1126 - compat-table/es6/typed-arrays.Float32Array.js: failed 1126 + compat-table/es6/typed-arrays.Float32Array.js: OK 1127 1127 compat-table/es6/typed-arrays.Float64Array.js: OK 1128 1128 compat-table/es6/typed-arrays.Int8Array.js: OK 1129 1129 compat-table/es6/typed-arrays.Int16Array.js: OK
+10
examples/spec/numbers.js
··· 28 28 29 29 test('Number()', Number('42'), 42); 30 30 test('Number() float', Number('3.14'), 3.14); 31 + test('Number() empty string', Number(''), 0); 32 + test('Number() signed hex invalid', Number.isNaN(Number('+0x10')), true); 31 33 test('Number() invalid', Number.isNaN(Number('abc')), true); 32 34 33 35 test('parseInt', parseInt('42'), 42); ··· 60 62 test('Number.isSafeInteger false', Number.isSafeInteger(9007199254740992), false); 61 63 62 64 test('toFixed', (3.14159).toFixed(2), '3.14'); 65 + test('decimal literal matches Number string parse', 0.7875 === Number('0.7875'), true); 66 + test('toFixed halfway below', (0.7875).toFixed(3), '0.787'); 67 + test('toFixed halfway above', (0.7876).toFixed(3), '0.788'); 68 + test('toFixed exposes correctly parsed double', (0.7875).toFixed(20), '0.78749999999999997780'); 69 + test('toFixed binary midpoint', (2.675).toFixed(2), '2.67'); 63 70 test('toPrecision', (123.456).toPrecision(4), '123.5'); 64 71 test('toExponential', (12345).toExponential(2), '1.23e+4'); 65 72 test('toString', (255).toString(16), 'ff'); ··· 67 74 test('integer literal method access with spaced dot', 27 .toString(), '27'); 68 75 test('exponent literal method access', 1e3.toString(), '1000'); 69 76 test('leading-dot literal method access', .5.toString(), '0.5'); 77 + testEvalSyntaxError('missing exponent digits syntax error', '1e'); 78 + testEvalSyntaxError('missing signed exponent digits syntax error', '1e+'); 79 + testEvalSyntaxError('dot exponent without digits syntax error', '1.e'); 70 80 testEvalSyntaxError('integer literal dot identifier syntax error', '27.toString()'); 71 81 testEvalSyntaxError('integer literal dot property syntax error', '27.a'); 72 82 testEvalSyntaxError('leading-dot literal identifier syntax error', '.5foo');
+31
include/numbers.h
··· 1 + #ifndef ANT_NUMBER_CONVERSION_H 2 + #define ANT_NUMBER_CONVERSION_H 3 + 4 + #include <stdbool.h> 5 + #include <stddef.h> 6 + 7 + #ifdef __cplusplus 8 + extern "C" { 9 + #endif 10 + 11 + typedef enum { 12 + ANT_NUMBER_PARSE_DECIMAL, 13 + ANT_NUMBER_PARSE_JS_NUMBER, 14 + ANT_NUMBER_PARSE_FLOAT_PREFIX, 15 + } ant_number_parse_mode_t; 16 + 17 + bool ant_number_parse( 18 + const char *str, size_t len, 19 + ant_number_parse_mode_t mode, 20 + double *out, size_t *processed 21 + ); 22 + 23 + size_t ant_number_to_shortest(double value, char *buf, size_t len); 24 + size_t ant_number_to_fixed(double value, int digits, char *buf, size_t len); 25 + size_t ant_number_to_precision(double value, int precision, char *buf, size_t len); 26 + size_t ant_number_to_exponential(double value, int digits, char *buf, size_t len); 27 + 28 + #ifdef __cplusplus 29 + } 30 + #endif 31 + #endif
+2 -1
meson.build
··· 1 - project('ant', 'c', default_options: [ 1 + project('ant', ['c', 'cpp'], default_options: [ 2 2 'buildtype=release', 3 3 'optimization=3', 4 4 'c_std=gnu23', 5 + 'cpp_std=gnu++23', 5 6 'default_library=static', 6 7 'b_lto=true', 7 8 'b_lto_threads=8',
+2
meson/deps/meson.build
··· 126 126 crprintf_dep = subproject('crprintf').get_variable('crprintf_dep') 127 127 uriparser_dep = subproject('uriparser').get_variable('uriparser_dep') 128 128 wamr_dep = subproject('wasm-micro-runtime').get_variable('wamr_dep') 129 + double_conversion_dep = subproject('double-conversion').get_variable('double_conversion_dep') 129 130 130 131 utf8proc_base_dep = subproject('utf8proc').get_variable('utf8proc_dep') 131 132 utf8proc_dep = declare_dependency( ··· 234 235 uriparser_dep, utf8proc_dep, ssl_dep, crypto_dep, 235 236 zlib_dep, brotli_common_dep, brotli_dec_dep, 236 237 brotli_enc_dep, uthash_dep, lmdb_dep, wamr_dep, 238 + double_conversion_dep, 237 239 ] + win_deps 238 240 239 241 if get_option('jit')
+1
sources.json
··· 2 2 "engine": { 3 3 "patterns": [ 4 4 "src/*.c", 5 + "src/*.cc", 5 6 "src/gc/*.c", 6 7 "src/highlight/*.c", 7 8 "src/esm/*.c",
+34 -310
src/ant.c
··· 18 18 #include "errors.h" 19 19 #include "descriptors.h" 20 20 #include "shapes.h" 21 + #include "numbers.h" 21 22 22 23 #include "gc.h" 23 24 #include "gc/objects.h" ··· 1932 1933 return n; 1933 1934 } 1934 1935 1935 - static size_t fix_exponent(char *buf, size_t n) { 1936 - char *e = strchr(buf, 'e'); 1937 - if (!e) return n; 1938 - 1939 - char *src = e + 1; 1940 - char *dst = src; 1941 - 1942 - if (*src == '+' || *src == '-') { 1943 - dst++; 1944 - src++; 1945 - } 1946 - 1947 - while (*src == '0' && src[1] != '\0') src++; 1948 - 1949 - if (src != dst) { 1950 - memmove(dst, src, strlen(src) + 1); 1951 - return strlen(buf); 1952 - } 1953 - return n; 1954 - } 1955 - 1956 - static size_t strnum_safe_integer(double dv, char *buf, size_t len) { 1957 - char temp[32]; 1958 - size_t pos = sizeof(temp); 1959 - uint64_t value = 0; 1960 - bool negative = signbit(dv) != 0; 1961 - 1962 - if (negative) dv = -dv; 1963 - value = (uint64_t)dv; 1964 - 1965 - do { 1966 - temp[--pos] = (char)('0' + (value % 10u)); 1967 - value /= 10u; 1968 - } while (value != 0); 1969 - 1970 - if (negative) temp[--pos] = '-'; 1971 - return cpy(buf, len, temp + pos, sizeof(temp) - pos); 1972 - } 1973 - 1974 1936 static size_t strnum(ant_value_t value, char *buf, size_t len) { 1975 1937 double dv = tod(value); 1976 - if (dv == 0.0) return cpy(buf, len, "0", 1); 1977 1938 1978 1939 if (__builtin_expect(isnan(dv), 0)) 1979 1940 return cpy(buf, len, "NaN", 3); 1941 + 1980 1942 if (__builtin_expect(isinf(dv), 0)) 1981 1943 return cpy(buf, len, dv > 0 ? "Infinity" : "-Infinity", dv > 0 ? 8 : 9); 1982 - 1983 - char temp[64]; 1984 - int sign = dv < 0 ? 1 : 0; 1985 - double adv = sign ? -dv : dv; 1986 - 1987 - double iv; 1988 - double frac = modf(adv, &iv); 1989 - if (frac == 0.0 && adv < 9007199254740992.0) { 1990 - return strnum_safe_integer(dv, buf, len); 1991 - } 1992 - 1993 - for (int prec = 1; prec <= 17; prec++) { 1994 - int n = snprintf(temp, sizeof(temp), "%.*g", prec, dv); 1995 - double parsed = strtod(temp, NULL); 1996 - if (parsed == dv) { 1997 - fix_exponent(temp, (size_t)n); 1998 - return cpy(buf, len, temp, strlen(temp)); 1999 - } 2000 - } 2001 - 2002 - int result = snprintf(temp, sizeof(temp), "%.17g", dv); 2003 - fix_exponent(temp, (size_t)result); 2004 - return cpy(buf, len, temp, strlen(temp)); 1944 + 1945 + return ant_number_to_shortest(dv, buf, len); 2005 1946 } 2006 1947 2007 1948 static inline ant_offset_t assert_flat_string_len(ant_value_t value, const char **out_ptr) { ··· 3315 3256 return (const char *)(uintptr_t)vdata(code_val); 3316 3257 } 3317 3258 3318 - static inline bool js_is_trim_space(char ch) { 3319 - return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; 3320 - } 3321 - 3322 - static inline bool js_try_parse_ascii_decimal_fast(const char *str, size_t len, double *out) { 3323 - size_t i = 0; 3324 - int sign = 1; 3325 - int int_digits = 0; 3326 - int frac_digits = 0; 3327 - 3328 - double value = 0.0; 3329 - double scale = 1.0; 3330 - bool saw_digit = false; 3331 - 3332 - if (len == 0) return false; 3333 - if (str[i] == '+' || str[i] == '-') { 3334 - sign = (str[i] == '-') ? -1 : 1; 3335 - i++; 3336 - if (i == len) return false; 3337 - } 3338 - 3339 - while (i < len && str[i] >= '0' && str[i] <= '9') { 3340 - if (int_digits >= 18) return false; 3341 - value = value * 10.0 + (double)(str[i] - '0'); 3342 - saw_digit = true; 3343 - int_digits++; i++; 3344 - } 3345 - 3346 - if (i < len && str[i] == '.') { 3347 - i++; 3348 - while (i < len && str[i] >= '0' && str[i] <= '9') { 3349 - if (frac_digits >= 18) return false; 3350 - scale *= 0.1; 3351 - value += (double)(str[i] - '0') * scale; 3352 - saw_digit = true; 3353 - frac_digits++; i++; 3354 - }} 3355 - 3356 - if (!saw_digit || i != len) return false; 3357 - *out = (sign < 0) ? -value : value; 3358 - 3359 - return true; 3360 - } 3361 - 3362 3259 double js_to_number(ant_t *js, ant_value_t arg) { 3363 3260 if (vtype(arg) == T_NULL) return 0.0; 3364 3261 if (vtype(arg) == T_UNDEF) return JS_NAN; ··· 3370 3267 if (vtype(arg) == T_STR) { 3371 3268 ant_flat_string_t *flat = ant_str_flat_ptr(arg); 3372 3269 const char *base = NULL; 3373 - const char *s = NULL; 3374 3270 const char *end = NULL; 3375 3271 3376 3272 if (flat) { 3377 3273 base = flat->bytes; 3378 - s = base; 3379 3274 end = base + flat->len; 3380 3275 } else { 3381 3276 ant_offset_t len = 0; 3382 3277 ant_offset_t off = vstr(js, arg, &len); 3383 3278 base = (const char *)(uintptr_t)off; 3384 - s = base; 3385 3279 end = base + len; 3386 3280 } 3387 3281 3388 - while (s < end && js_is_trim_space(*s)) s++; 3389 - if (s == end) return 0.0; 3282 + double val = JS_NAN; if ( 3283 + ant_number_parse(base, (size_t)(end - base), 3284 + ANT_NUMBER_PARSE_JS_NUMBER, &val, NULL) 3285 + ) return val; 3390 3286 3391 - double fast_val = 0.0; 3392 - if (js_try_parse_ascii_decimal_fast(s, (size_t)(end - s), &fast_val)) 3393 - return fast_val; 3394 - 3395 - char *parse_end = NULL; 3396 - double val = strtod(s, &parse_end); 3397 - while (parse_end < end && js_is_trim_space(*parse_end)) parse_end++; 3398 - 3399 - return (parse_end == s || parse_end != end) ? JS_NAN : val; 3287 + return JS_NAN; 3400 3288 } 3401 3289 3402 3290 if (vtype(arg) == T_OBJ || vtype(arg) == T_ARR) { ··· 11650 11538 } 11651 11539 } 11652 11540 11653 - bool negative = d < 0; 11654 - if (negative) d = -d; 11655 - 11656 - if (d >= 1e21) { 11541 + if (fabs(d) >= 1e21) { 11657 11542 char buf[64]; 11658 - snprintf(buf, sizeof(buf), "%.0f", negative ? -d : d); 11659 - return js_mkstr(js, buf, strlen(buf)); 11660 - } 11661 - 11662 - double scale = pow(10, digits); 11663 - double scaled = d * scale; 11664 - double rounded = floor(scaled + 0.5); 11665 - 11666 - char digit_buf[128]; 11667 - snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded); 11668 - int digit_len = (int)strlen(digit_buf); 11669 - 11670 - while (digit_len < digits + 1) { 11671 - memmove(digit_buf + 1, digit_buf, digit_len + 1); 11672 - digit_buf[0] = '0'; 11673 - digit_len++; 11674 - } 11675 - 11676 - char buf[128]; 11677 - int pos = 0; 11678 - 11679 - if (negative && rounded != 0) buf[pos++] = '-'; 11680 - int int_digits = digit_len - digits; 11681 - if (int_digits <= 0) int_digits = 1; 11682 - 11683 - for (int i = 0; i < int_digits; i++) { 11684 - buf[pos++] = digit_buf[i]; 11543 + size_t len = strnum(num, buf, sizeof(buf)); 11544 + return js_mkstr(js, buf, len); 11685 11545 } 11546 + 11547 + char buf[160]; 11548 + size_t len = ant_number_to_fixed(d, digits, buf, sizeof(buf)); 11549 + if (len == 0) return js_mkerr(js, "number formatting failed"); 11686 11550 11687 - if (digits > 0) { 11688 - buf[pos++] = '.'; 11689 - for (int i = int_digits; i < digit_len; i++) { 11690 - buf[pos++] = digit_buf[i]; 11691 - } 11692 - } 11693 - 11694 - buf[pos] = '\0'; 11695 - return js_mkstr(js, buf, pos); 11551 + return js_mkstr(js, buf, len); 11696 11552 } 11697 11553 11698 11554 static ant_value_t builtin_number_toPrecision(ant_t *js, ant_value_t *args, int nargs) { ··· 11714 11570 return js_mkerr_typed(js, JS_ERR_RANGE, "toPrecision() argument must be between 1 and 100"); 11715 11571 } 11716 11572 11717 - bool negative = d < 0; 11718 - if (negative) d = -d; 11573 + char buf[160]; 11574 + size_t len = ant_number_to_precision(d, precision, buf, sizeof(buf)); 11575 + if (len == 0) return js_mkerr(js, "number formatting failed"); 11719 11576 11720 - if (d == 0) { 11721 - char buf[128]; 11722 - int pos = 0; 11723 - if (negative) buf[pos++] = '-'; 11724 - buf[pos++] = '0'; 11725 - if (precision > 1) { 11726 - buf[pos++] = '.'; 11727 - for (int i = 1; i < precision; i++) buf[pos++] = '0'; 11728 - } 11729 - buf[pos] = '\0'; 11730 - return js_mkstr(js, buf, pos); 11731 - } 11732 - 11733 - int exp = (int) floor(log10(d)); 11734 - bool use_exp = (exp < -(precision - 1) - 1) || (exp >= precision); 11735 - 11736 - if (use_exp) { 11737 - double mantissa = d / pow(10, exp); 11738 - double scale = pow(10, precision - 1); 11739 - double rounded = floor(mantissa * scale + 0.5); 11740 - 11741 - if (rounded >= scale * 10) { 11742 - rounded /= 10; 11743 - exp++; 11744 - } 11745 - 11746 - char digit_buf[32]; 11747 - snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded); 11748 - int digit_len = (int)strlen(digit_buf); 11749 - 11750 - char buf[128]; 11751 - int pos = 0; 11752 - if (negative) buf[pos++] = '-'; 11753 - buf[pos++] = digit_buf[0]; 11754 - if (precision > 1) { 11755 - buf[pos++] = '.'; 11756 - for (int i = 1; i < precision; i++) { 11757 - buf[pos++] = (i < digit_len) ? digit_buf[i] : '0'; 11758 - } 11759 - } 11760 - buf[pos++] = 'e'; 11761 - buf[pos++] = (exp >= 0) ? '+' : '-'; 11762 - if (exp < 0) exp = -exp; 11763 - snprintf(buf + pos, sizeof(buf) - pos, "%d", exp); 11764 - return js_mkstr(js, buf, strlen(buf)); 11765 - } else { 11766 - int digits_after_point = precision - exp - 1; 11767 - if (digits_after_point < 0) digits_after_point = 0; 11768 - 11769 - double scale = pow(10, digits_after_point); 11770 - double rounded = floor(d * scale + 0.5); 11771 - 11772 - char digit_buf[64]; 11773 - snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded); 11774 - int digit_len = (int)strlen(digit_buf); 11775 - 11776 - while (digit_len < digits_after_point + 1) { 11777 - memmove(digit_buf + 1, digit_buf, digit_len + 1); 11778 - digit_buf[0] = '0'; 11779 - digit_len++; 11780 - } 11781 - 11782 - char buf[128]; 11783 - int pos = 0; 11784 - if (negative) buf[pos++] = '-'; 11785 - 11786 - int int_digits = digit_len - digits_after_point; 11787 - for (int i = 0; i < int_digits; i++) { 11788 - buf[pos++] = digit_buf[i]; 11789 - } 11790 - 11791 - if (digits_after_point > 0) { 11792 - buf[pos++] = '.'; 11793 - for (int i = int_digits; i < digit_len; i++) { 11794 - buf[pos++] = digit_buf[i]; 11795 - } 11796 - } 11797 - 11798 - buf[pos] = '\0'; 11799 - return js_mkstr(js, buf, pos); 11800 - } 11577 + return js_mkstr(js, buf, len); 11801 11578 } 11802 11579 11803 11580 static ant_value_t builtin_number_toExponential(ant_t *js, ant_value_t *args, int nargs) { ··· 11816 11593 } 11817 11594 } 11818 11595 11819 - bool negative = d < 0; 11820 - if (negative) d = -d; 11821 - 11822 - int exp = 0; 11823 - if (d != 0) { 11824 - exp = (int) floor(log10(d)); 11825 - double test = d / pow(10, exp); 11826 - if (test >= 10) { exp++; test /= 10; } 11827 - if (test < 1) { exp--; test *= 10; } 11828 - } 11829 - 11830 - if (digits < 0) { 11831 - char temp[32]; 11832 - snprintf(temp, sizeof(temp), "%.15g", d); 11833 - int sig = 0; 11834 - for (int i = 0; temp[i] && temp[i] != 'e' && temp[i] != 'E'; i++) { 11835 - if (temp[i] == '.') continue; 11836 - if (temp[i] >= '0' && temp[i] <= '9') if (temp[i] != '0' || sig > 0) sig++; 11837 - } 11838 - digits = sig > 0 ? sig - 1 : 0; 11839 - if (digits > 20) digits = 20; 11840 - } 11841 - 11842 - double mantissa = d / pow(10, exp); 11843 - double scale = pow(10, digits); 11844 - double scaled = mantissa * scale; 11845 - double rounded = floor(scaled + 0.5); 11846 - 11847 - if (rounded >= scale * 10) { 11848 - rounded /= 10; 11849 - exp++; 11850 - } 11851 - 11852 - char buf[64]; 11853 - int pos = 0; 11854 - 11855 - if (negative) buf[pos++] = '-'; 11596 + char buf[160]; 11597 + size_t len = ant_number_to_exponential(d, digits, buf, sizeof(buf)); 11598 + if (len == 0) return js_mkerr(js, "number formatting failed"); 11856 11599 11857 - char digit_buf[32]; 11858 - snprintf(digit_buf, sizeof(digit_buf), "%.0f", rounded); 11859 - int digit_len = (int)strlen(digit_buf); 11860 - 11861 - while (digit_len < digits + 1) { 11862 - memmove(digit_buf + 1, digit_buf, digit_len + 1); 11863 - digit_buf[0] = '0'; 11864 - digit_len++; 11865 - } 11866 - 11867 - buf[pos++] = digit_buf[0]; 11868 - 11869 - if (digits > 0) { 11870 - buf[pos++] = '.'; 11871 - for (int i = 1; i <= digits; i++) { 11872 - buf[pos++] = (i < digit_len) ? digit_buf[i] : '0'; 11873 - } 11874 - } 11875 - 11876 - buf[pos++] = 'e'; 11877 - buf[pos++] = (exp >= 0) ? '+' : '-'; 11878 - if (exp < 0) exp = -exp; 11879 - snprintf(buf + pos, sizeof(buf) - pos, "%d", exp); 11880 - 11881 - return js_mkstr(js, buf, strlen(buf)); 11600 + return js_mkstr(js, buf, len); 11882 11601 } 11883 11602 11884 11603 static ant_value_t builtin_number_valueOf(ant_t *js, ant_value_t *args, int nargs) { ··· 12022 11741 12023 11742 if (i >= str_len) return tov(JS_NAN); 12024 11743 12025 - char *end; 12026 - double result = strtod(&str[i], &end); 11744 + double result = JS_NAN; 11745 + size_t processed = 0; 12027 11746 12028 - if (end == &str[i]) return tov(JS_NAN); 12029 - 11747 + if (!ant_number_parse( 11748 + str + i, 11749 + (size_t)(str_len - i), 11750 + ANT_NUMBER_PARSE_FLOAT_PREFIX, 11751 + &result, &processed 11752 + ) || processed == 0) return tov(JS_NAN); 11753 + 12030 11754 return tov(result); 12031 11755 } 12032 11756
+249
src/numbers.cc
··· 1 + #include "numbers.h" 2 + 3 + #include <cstring> 4 + #include <limits> 5 + 6 + #include "double-conversion/utils.h" 7 + #include "double-conversion/double-to-string.h" 8 + #include "double-conversion/string-to-double.h" 9 + 10 + using double_conversion::StringBuilder; 11 + using double_conversion::DoubleToStringConverter; 12 + using double_conversion::StringToDoubleConverter; 13 + 14 + struct JsTrimToken { 15 + const char *bytes; 16 + size_t len; 17 + }; 18 + 19 + static constexpr size_t kShortestBufferSize = 20 + DoubleToStringConverter::kMaxCharsEcmaScriptShortest + 1; 21 + 22 + static constexpr size_t kFixedBufferSize = 23 + 1 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint + 1 + 24 + DoubleToStringConverter::kMaxFixedDigitsAfterPoint + 1; 25 + 26 + static constexpr size_t kPrecisionBufferSize = 27 + DoubleToStringConverter::kMaxPrecisionDigits + 7 + 1; 28 + 29 + static constexpr size_t kExponentialBufferSize = 30 + DoubleToStringConverter::kMaxExponentialDigits + 8 + 1; 31 + 32 + #define JS_TRIM_TOKEN(bytes) { bytes, sizeof(bytes) - 1 } 33 + static constexpr JsTrimToken kJsStringTrimTokens[] = { 34 + JS_TRIM_TOKEN("\t"), 35 + JS_TRIM_TOKEN("\n"), 36 + JS_TRIM_TOKEN("\v"), 37 + JS_TRIM_TOKEN("\f"), 38 + JS_TRIM_TOKEN("\r"), 39 + JS_TRIM_TOKEN(" "), 40 + JS_TRIM_TOKEN("\xc2""\xa0"), 41 + JS_TRIM_TOKEN("\xe1""\x9a""\x80"), 42 + JS_TRIM_TOKEN("\xe2""\x80""\x80"), 43 + JS_TRIM_TOKEN("\xe2""\x80""\x81"), 44 + JS_TRIM_TOKEN("\xe2""\x80""\x82"), 45 + JS_TRIM_TOKEN("\xe2""\x80""\x83"), 46 + JS_TRIM_TOKEN("\xe2""\x80""\x84"), 47 + JS_TRIM_TOKEN("\xe2""\x80""\x85"), 48 + JS_TRIM_TOKEN("\xe2""\x80""\x86"), 49 + JS_TRIM_TOKEN("\xe2""\x80""\x87"), 50 + JS_TRIM_TOKEN("\xe2""\x80""\x88"), 51 + JS_TRIM_TOKEN("\xe2""\x80""\x89"), 52 + JS_TRIM_TOKEN("\xe2""\x80""\x8a"), 53 + JS_TRIM_TOKEN("\xe2""\x80""\xa8"), 54 + JS_TRIM_TOKEN("\xe2""\x80""\xa9"), 55 + JS_TRIM_TOKEN("\xe2""\x80""\xaf"), 56 + JS_TRIM_TOKEN("\xe2""\x81""\x9f"), 57 + JS_TRIM_TOKEN("\xe3""\x80""\x80"), 58 + JS_TRIM_TOKEN("\xef""\xbb""\xbf"), 59 + }; 60 + #undef JS_TRIM_TOKEN 61 + 62 + static size_t js_string_trim_prefix_len(const char *str, size_t len) { 63 + for (const JsTrimToken &token : kJsStringTrimTokens) 64 + if (len >= token.len && std::memcmp(str, token.bytes, token.len) == 0) return token.len; 65 + return 0; 66 + } 67 + 68 + static size_t js_string_trim_suffix_len(const char *str, size_t len) { 69 + for (const JsTrimToken &token : kJsStringTrimTokens) 70 + if (len >= token.len && std::memcmp(str + len - token.len, token.bytes, token.len) == 0) return token.len; 71 + return 0; 72 + } 73 + 74 + static void trim_js_string_whitespace(const char **str, size_t *len, bool trim_trailing, size_t *leading) { 75 + size_t lead = 0; 76 + 77 + while (*len > 0) { 78 + size_t n = js_string_trim_prefix_len(*str, *len); 79 + if (n == 0) break; 80 + *str += n; *len -= n; lead += n; 81 + } 82 + 83 + while (trim_trailing && *len > 0) { 84 + size_t n = js_string_trim_suffix_len(*str, *len); 85 + if (n == 0) break; 86 + *len -= n; 87 + } 88 + 89 + if (leading) *leading = lead; 90 + } 91 + 92 + static bool ant_starts_with_nondecimal_prefix(const char *str, size_t len) { 93 + return 94 + len >= 2 && str[0] == '0' && 95 + ((str[1] | 0x20) == 'x' || 96 + (str[1] | 0x20) == 'b' || (str[1] | 0x20) == 'o'); 97 + } 98 + 99 + static bool ant_parse_radix_integer(const char *str, size_t len, int radix, double *out) { 100 + if (!str || len == 0 || !out) return false; 101 + double value = 0.0; 102 + 103 + for (size_t i = 0; i < len; i++) { 104 + unsigned char ch = (unsigned char)str[i]; 105 + int digit = -1; 106 + if (ch >= '0' && ch <= '9') digit = ch - '0'; 107 + else if (ch >= 'a' && ch <= 'z') digit = ch - 'a' + 10; 108 + else if (ch >= 'A' && ch <= 'Z') digit = ch - 'A' + 10; 109 + if (digit < 0 || digit >= radix) return false; 110 + value = value * (double)radix + (double)digit; 111 + } 112 + 113 + *out = value; 114 + return true; 115 + } 116 + 117 + static bool ant_parse_js_number_prefix(const char *str, size_t len, double *out) { 118 + if (len < 3 || str[0] != '0') return false; 119 + 120 + char kind = (char)(str[1] | 0x20); 121 + int radix = kind == 'b' ? 2 : (kind == 'o' ? 8 : 0); 122 + if (radix == 0) return false; 123 + 124 + double value = 0.0; 125 + if (!ant_parse_radix_integer(str + 2, len - 2, radix, &value)) 126 + return false; 127 + 128 + *out = value; 129 + return true; 130 + } 131 + 132 + extern "C" bool ant_number_parse( 133 + const char *str, size_t len, 134 + ant_number_parse_mode_t mode, 135 + double *out, size_t *processed 136 + ) { 137 + if (processed) *processed = 0; 138 + if (!str || !out) return false; 139 + 140 + size_t leading = 0; 141 + if (mode == ANT_NUMBER_PARSE_JS_NUMBER || mode == ANT_NUMBER_PARSE_FLOAT_PREFIX) { 142 + trim_js_string_whitespace(&str, &len, mode == ANT_NUMBER_PARSE_JS_NUMBER, &leading); 143 + 144 + if (mode == ANT_NUMBER_PARSE_JS_NUMBER && len == 0) { 145 + *out = 0.0; 146 + if (processed) *processed = leading; 147 + return true; 148 + }} 149 + 150 + if ( 151 + mode == ANT_NUMBER_PARSE_JS_NUMBER && len >= 3 && 152 + (str[0] == '+' || str[0] == '-') && 153 + ant_starts_with_nondecimal_prefix(str + 1, len - 1) 154 + ) return false; 155 + 156 + if (mode == ANT_NUMBER_PARSE_JS_NUMBER && ant_parse_js_number_prefix(str, len, out)) { 157 + if (processed) *processed = len; 158 + return true; 159 + } 160 + 161 + int flags = 0; 162 + if (mode == ANT_NUMBER_PARSE_JS_NUMBER) 163 + flags = 164 + StringToDoubleConverter::ALLOW_HEX | 165 + StringToDoubleConverter::ALLOW_LEADING_SPACES | 166 + StringToDoubleConverter::ALLOW_TRAILING_SPACES; 167 + else if (mode == ANT_NUMBER_PARSE_FLOAT_PREFIX) 168 + flags = 169 + StringToDoubleConverter::ALLOW_LEADING_SPACES | 170 + StringToDoubleConverter::ALLOW_TRAILING_JUNK; 171 + 172 + StringToDoubleConverter converter( 173 + flags, 0.0, 174 + std::numeric_limits<double>::quiet_NaN(), 175 + "Infinity", "NaN" 176 + ); 177 + 178 + int count = 0; 179 + double value = converter.StringToDouble(str, (int)len, &count); 180 + 181 + if (count <= 0) return false; 182 + if (mode != ANT_NUMBER_PARSE_FLOAT_PREFIX && (size_t)count != len) return false; 183 + 184 + *out = value; 185 + if (processed) *processed = leading + (size_t)count; 186 + 187 + return true; 188 + } 189 + 190 + static inline size_t copy_truncated_number_result(char *dst, size_t dstlen, const char *src, size_t srclen) { 191 + if (!dst || dstlen == 0) return srclen; 192 + size_t n = srclen < dstlen - 1 ? srclen : dstlen - 1; 193 + 194 + if (n > 0) std::memcpy(dst, src, n); 195 + dst[n] = '\0'; 196 + 197 + return srclen; 198 + } 199 + 200 + template <typename Format> 201 + static size_t ant_format_number( 202 + char *buf, 203 + size_t len, 204 + char *scratch, 205 + size_t scratch_len, 206 + Format format 207 + ) { 208 + char *out = (buf && len >= scratch_len) ? buf : scratch; 209 + size_t out_len = (out == buf) ? len : scratch_len; 210 + 211 + StringBuilder builder(out, (int)out_len); 212 + bool ok = format(&builder); 213 + if (!ok) return 0; 214 + 215 + int pos = builder.position(); 216 + if (pos < 0) return 0; 217 + builder.Finalize(); 218 + 219 + if (out == buf) return (size_t)pos; 220 + return copy_truncated_number_result(buf, len, scratch, (size_t)pos); 221 + } 222 + 223 + extern "C" size_t ant_number_to_shortest(double value, char *buf, size_t len) { 224 + char scratch[kShortestBufferSize]; 225 + return ant_format_number(buf, len, scratch, sizeof(scratch), [value](StringBuilder *builder) { 226 + return DoubleToStringConverter::EcmaScriptConverter().ToShortest(value, builder); 227 + }); 228 + } 229 + 230 + extern "C" size_t ant_number_to_fixed(double value, int digits, char *buf, size_t len) { 231 + char scratch[kFixedBufferSize]; 232 + return ant_format_number(buf, len, scratch, sizeof(scratch), [value, digits](StringBuilder *builder) { 233 + return DoubleToStringConverter::EcmaScriptConverter().ToFixed(value, digits, builder); 234 + }); 235 + } 236 + 237 + extern "C" size_t ant_number_to_precision(double value, int precision, char *buf, size_t len) { 238 + char scratch[kPrecisionBufferSize]; 239 + return ant_format_number(buf, len, scratch, sizeof(scratch), [value, precision](StringBuilder *builder) { 240 + return DoubleToStringConverter::EcmaScriptConverter().ToPrecision(value, precision, builder); 241 + }); 242 + } 243 + 244 + extern "C" size_t ant_number_to_exponential(double value, int digits, char *buf, size_t len) { 245 + char scratch[kExponentialBufferSize]; 246 + return ant_format_number(buf, len, scratch, sizeof(scratch), [value, digits](StringBuilder *builder) { 247 + return DoubleToStringConverter::EcmaScriptConverter().ToExponential(value, digits, builder); 248 + }); 249 + }
+101 -52
src/silver/lexer.c
··· 5 5 #include "tokens.h" 6 6 #include "utf8.h" 7 7 #include "errors.h" 8 + #include "numbers.h" 8 9 9 10 #include <runtime.h> 10 - #include <math.h> 11 + #include <stdlib.h> 11 12 #include <string.h> 12 13 13 14 void sv_lexer_init(sv_lexer_t *lx, ant_t *js, const char *code, ant_offset_t clen, bool strict) { ··· 561 562 return sv_parsekeyword(buf, *tlen); 562 563 } 563 564 564 - static inline ant_offset_t parse_decimal(const char *buf, ant_offset_t maxlen, double *out) { 565 - uint64_t int_part = 0, frac_part = 0; 566 - int frac_digits = 0; 567 - ant_offset_t i = 0; 565 + static inline bool is_decimal_literal_char(char ch) { 566 + return IS_DIGIT(ch) || ch == '_'; 567 + } 568 + 569 + static inline bool has_numeric_separator(const char *src, ant_offset_t len) { 570 + return memchr(src, '_', (size_t)len) != NULL; 571 + } 568 572 569 - while (i < maxlen && (IS_DIGIT(buf[i]) || buf[i] == '_')) { 570 - if (buf[i] != '_') int_part = int_part * 10 + (buf[i] - '0'); 571 - i++; 573 + static inline const char *scan_decimal_literal_chars(const char *p, const char *end) { 574 + while (p < end && is_decimal_literal_char(*p)) p++; 575 + return p; 576 + } 577 + 578 + static inline ant_offset_t scan_decimal_literal(const char *buf, ant_offset_t maxlen) { 579 + const char *start = buf; 580 + const char *end = buf + maxlen; 581 + const char *p = scan_decimal_literal_chars(buf, end); 582 + 583 + if (p < end && *p == '.') { 584 + p = scan_decimal_literal_chars(p + 1, end); 572 585 } 573 586 574 - if (i < maxlen && buf[i] == '.') { 575 - i++; 576 - while (i < maxlen && (IS_DIGIT(buf[i]) || buf[i] == '_')) { 577 - if (buf[i] != '_') { frac_part = frac_part * 10 + (buf[i] - '0'); frac_digits++; } 578 - i++; 579 - } 587 + if (p < end && ((*p | 0x20) == 'e')) { 588 + p++; 589 + if (p < end && (*p == '+' || *p == '-')) p++; 590 + p = scan_decimal_literal_chars(p, end); 580 591 } 581 592 582 - static const double neg_pow10[] = { 583 - 1e0,1e-1,1e-2,1e-3,1e-4,1e-5,1e-6,1e-7,1e-8,1e-9,1e-10, 584 - 1e-11,1e-12,1e-13,1e-14,1e-15,1e-16,1e-17,1e-18,1e-19,1e-20 585 - }; 593 + return (ant_offset_t)(p - start); 594 + } 595 + 596 + static inline size_t copy_without_numeric_separators(const char *src, ant_offset_t len, char *dst) { 597 + const char *end = src + len; 598 + char *start = dst; 599 + 600 + while (src < end) { 601 + char ch = *src++; 602 + if (ch != '_') *dst++ = ch; 603 + } 604 + 605 + return (size_t)(dst - start); 606 + } 607 + 608 + typedef struct { 609 + ant_offset_t len; 610 + double value; 611 + bool ok; 612 + } decimal_literal_t; 613 + 614 + static inline decimal_literal_t parse_decimal_literal(const char *buf, ant_offset_t maxlen) { 615 + ant_offset_t toklen = scan_decimal_literal(buf, maxlen); 616 + const char *digits = buf; 617 + 618 + size_t digits_len = (size_t)toklen; 619 + char stack_buf[128]; 620 + char *clean = NULL; 586 621 587 - static const double pos_pow10[] = { 588 - 1e0,1e1,1e2,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10, 589 - 1e11,1e12,1e13,1e14,1e15,1e16,1e17,1e18,1e19,1e20 622 + decimal_literal_t result = { 623 + .len = toklen, 624 + .value = 0.0, 625 + .ok = false 590 626 }; 591 627 592 - double val = (double)int_part; 593 - if (frac_digits > 0) { 594 - val += (frac_digits <= 20) 595 - ? (double)frac_part * neg_pow10[frac_digits] 596 - : (double)frac_part * pow(10.0, -frac_digits); 628 + if (has_numeric_separator(buf, toklen)) { 629 + clean = stack_buf; 630 + if (toklen > (ant_offset_t)sizeof(stack_buf)) { 631 + clean = malloc((size_t)toklen); 632 + if (!clean) return result; 633 + } 634 + 635 + digits_len = copy_without_numeric_separators(buf, toklen, clean); 636 + digits = clean; 597 637 } 598 638 599 - if (i < maxlen && (buf[i] == 'e' || buf[i] == 'E')) { 600 - i++; 601 - int exp_sign = 1, exp_val = 0; 602 - if (i < maxlen && (buf[i] == '+' || buf[i] == '-')) { 603 - exp_sign = (buf[i] == '-') ? -1 : 1; 604 - i++; 605 - } 606 - while (i < maxlen && (IS_DIGIT(buf[i]) || buf[i] == '_')) { 607 - if (buf[i] != '_') exp_val = exp_val * 10 + (buf[i] - '0'); 608 - i++; 609 - } 610 - if (exp_val <= 20) { 611 - val = (exp_sign > 0) ? val * pos_pow10[exp_val] : val * neg_pow10[exp_val]; 612 - } else val *= pow(10.0, exp_sign * exp_val); 613 - } 639 + result.ok = ant_number_parse( 640 + digits, digits_len, 641 + ANT_NUMBER_PARSE_DECIMAL, 642 + &result.value, NULL 643 + ); 614 644 615 - *out = val; 616 - return i; 645 + if (clean && clean != stack_buf) free(clean); 646 + return result; 617 647 } 618 648 619 649 static inline ant_offset_t parse_binary(const char *buf, ant_offset_t maxlen, double *out) { ··· 691 721 lx->st.tok = TOK_ERR; 692 722 lx->st.tlen = 1; 693 723 return TOK_ERR; 694 - } else numlen = parse_decimal(buf, remaining, &value); 695 - } else numlen = parse_decimal(buf, remaining, &value); 724 + } else { 725 + decimal_literal_t literal = parse_decimal_literal(buf, remaining); 726 + numlen = literal.len; 727 + if (!literal.ok) { 728 + lx->st.tok = TOK_ERR; 729 + lx->st.tlen = numlen; 730 + return TOK_ERR; 731 + } 732 + value = literal.value; 733 + } 734 + } else { 735 + decimal_literal_t literal = parse_decimal_literal(buf, remaining); 736 + numlen = literal.len; 737 + if (!literal.ok) { 738 + lx->st.tok = TOK_ERR; 739 + lx->st.tlen = numlen; 740 + return TOK_ERR; 741 + } 742 + value = literal.value; 743 + } 696 744 697 745 lx->st.tval = tov(value); 698 746 ant_offset_t toklen = numlen; ··· 1042 1090 case '.': 1043 1091 if (MATCH3('.','.', '.')) { lx->st.tok = TOK_REST; lx->st.tlen = 3; } 1044 1092 else if (rem > 1 && IS_DIGIT(buf[1])) { 1045 - double val; 1046 - ant_offset_t numlen = parse_decimal(buf, rem, &val); 1047 - if (number_literal_has_invalid_tail(buf, rem, numlen)) { 1093 + decimal_literal_t literal = parse_decimal_literal(buf, rem); 1094 + if (!literal.ok || number_literal_has_invalid_tail(buf, rem, literal.len)) { 1048 1095 lx->st.tok = TOK_ERR; 1049 - lx->st.tlen = numlen; 1096 + lx->st.tlen = literal.len; 1050 1097 } else { 1051 - lx->st.tlen = numlen; 1052 - lx->st.tval = tov(val); 1098 + lx->st.tlen = literal.len; 1099 + lx->st.tval = tov(literal.value); 1053 1100 lx->st.tok = TOK_NUMBER; 1054 1101 } 1102 + } else { 1103 + lx->st.tok = TOK_DOT; 1104 + lx->st.tlen = 1; 1055 1105 } 1056 - else { lx->st.tok = TOK_DOT; lx->st.tlen = 1; } 1057 1106 break; 1058 1107 1059 1108 default:
+146
tests/bench_number_conversion.js
··· 1 + const now = 2 + typeof performance !== "undefined" && performance && typeof performance.now === "function" 3 + ? () => performance.now() 4 + : () => Date.now(); 5 + 6 + const scale = 7 + typeof process !== "undefined" && process.argv && process.argv.length > 2 8 + ? Math.max(1, Number(process.argv[2]) || 1) 9 + : 1; 10 + 11 + let sink = 0; 12 + 13 + function mixNumber(value) { 14 + sink = (sink + (value * 1000003) | 0) ^ (sink << 5); 15 + } 16 + 17 + function mixString(value) { 18 + sink = (sink + value.length * 33 + value.charCodeAt(value.length - 1)) | 0; 19 + } 20 + 21 + function bench(name, iterations, fn) { 22 + const warmup = Math.max(1, iterations >> 4); 23 + fn(warmup); 24 + 25 + const start = now(); 26 + const ops = fn(iterations); 27 + const elapsed = now() - start; 28 + const nsPerOp = elapsed > 0 ? (elapsed * 1e6) / ops : 0; 29 + const opsPerSec = elapsed > 0 ? (ops * 1000) / elapsed : 0; 30 + 31 + console.log( 32 + name + 33 + ": " + 34 + elapsed.toFixed(2) + 35 + "ms (" + 36 + ops + 37 + " ops, " + 38 + nsPerOp.toFixed(2) + 39 + " ns/op, " + 40 + opsPerSec.toFixed(0) + 41 + " ops/s)" 42 + ); 43 + } 44 + 45 + const decimalStrings = [ 46 + "0.7875", 47 + "2.675", 48 + "123456789.125", 49 + "-0.0000033333333333333333", 50 + "1.7976931348623157e308", 51 + "5e-324", 52 + " 42.5 ", 53 + "0x10", 54 + "0b101010", 55 + "0o755", 56 + ]; 57 + 58 + const floatStrings = [ 59 + "0.7875px", 60 + "2.675 and change", 61 + " Infinity!", 62 + "-0.0000033333333333333333ms", 63 + "123456789.125;", 64 + "5e-324end", 65 + ]; 66 + 67 + const literalSources = [ 68 + "0.7875", 69 + "2.675", 70 + "123456789.125", 71 + "-0.0000033333333333333333", 72 + "1.7976931348623157e308", 73 + "5e-324", 74 + "1_234_567.8_9", 75 + "1.e+1", 76 + ]; 77 + 78 + const numbers = [ 79 + 0.7875, 80 + 0.7876, 81 + 2.675, 82 + 123456789.125, 83 + -0.0000033333333333333333, 84 + 1.7976931348623157e308, 85 + 5e-324, 86 + Math.PI, 87 + ]; 88 + 89 + console.log("Number conversion benchmark (scale " + scale + ")"); 90 + 91 + bench("Number(string)", 500000 * scale, n => { 92 + let sum = 0; 93 + for (let i = 0; i < n; i++) { 94 + sum += Number(decimalStrings[i % decimalStrings.length]); 95 + } 96 + mixNumber(sum); 97 + return n; 98 + }); 99 + 100 + bench("parseFloat(prefix)", 500000 * scale, n => { 101 + let sum = 0; 102 + for (let i = 0; i < n; i++) { 103 + sum += parseFloat(floatStrings[i % floatStrings.length]); 104 + } 105 + mixNumber(sum); 106 + return n; 107 + }); 108 + 109 + bench("eval(decimal literal)", 60000 * scale, n => { 110 + let sum = 0; 111 + for (let i = 0; i < n; i++) { 112 + sum += eval(literalSources[i % literalSources.length]); 113 + } 114 + mixNumber(sum); 115 + return n; 116 + }); 117 + 118 + bench("String(number)", 500000 * scale, n => { 119 + for (let i = 0; i < n; i++) { 120 + mixString(String(numbers[i & 7])); 121 + } 122 + return n; 123 + }); 124 + 125 + bench("number.toFixed(20)", 250000 * scale, n => { 126 + for (let i = 0; i < n; i++) { 127 + mixString(numbers[i & 7].toFixed(20)); 128 + } 129 + return n; 130 + }); 131 + 132 + bench("number.toPrecision(17)", 250000 * scale, n => { 133 + for (let i = 0; i < n; i++) { 134 + mixString(numbers[i & 7].toPrecision(17)); 135 + } 136 + return n; 137 + }); 138 + 139 + bench("number.toExponential(20)", 250000 * scale, n => { 140 + for (let i = 0; i < n; i++) { 141 + mixString(numbers[i & 7].toExponential(20)); 142 + } 143 + return n; 144 + }); 145 + 146 + console.log("sink:", sink);
+9
vendor/double-conversion.wrap
··· 1 + [wrap-file] 2 + directory = double-conversion-3.4.0 3 + source_url = https://github.com/google/double-conversion/archive/refs/tags/v3.4.0.tar.gz 4 + source_filename = double-conversion-3.4.0.tar.gz 5 + source_hash = 42fd4d980ea86426e457b24bdfa835a6f5ad9517ddb01cdb42b99ab9c8dd5dc9 6 + patch_directory = double-conversion 7 + 8 + [provide] 9 + double-conversion = double_conversion_dep
+30
vendor/packagefiles/double-conversion/meson.build
··· 1 + project('double-conversion', 'cpp', 2 + default_options: [ 3 + 'cpp_std=c++17', 4 + 'warning_level=0', 5 + 'default_library=static', 6 + ] 7 + ) 8 + 9 + double_conversion_inc = include_directories('.') 10 + 11 + double_conversion_lib = static_library( 12 + 'double-conversion', 13 + files( 14 + 'double-conversion/bignum.cc', 15 + 'double-conversion/bignum-dtoa.cc', 16 + 'double-conversion/cached-powers.cc', 17 + 'double-conversion/double-to-string.cc', 18 + 'double-conversion/fast-dtoa.cc', 19 + 'double-conversion/fixed-dtoa.cc', 20 + 'double-conversion/string-to-double.cc', 21 + 'double-conversion/strtod.cc', 22 + ), 23 + include_directories: double_conversion_inc, 24 + install: false, 25 + ) 26 + 27 + double_conversion_dep = declare_dependency( 28 + link_with: double_conversion_lib, 29 + include_directories: double_conversion_inc, 30 + )