diff options
author | Serge Aleynikov <saleyn@gmail.com> | 2013-01-21 15:36:43 +0100 |
---|---|---|
committer | Björn-Egil Dahlberg <egil@erlang.org> | 2013-01-21 15:36:43 +0100 |
commit | ab27e8699ef2a2bafe574158200993f184de3dc2 (patch) | |
tree | a4365e182109d25ce049244b220844705b7d2a25 /erts/emulator | |
parent | 9f461fbaf0be7aba7c0b8b89be1f0b6f1141b7a5 (diff) | |
download | erlang-ab27e8699ef2a2bafe574158200993f184de3dc2.tar.gz |
Text representation of a float formatted using given options.
This BIF solves a problem of float_to_list/1 that doesn't allow
specifying the number of digits after the decimal point when
formatting floats.
float_to_list(Float, Options) -> string()
Float = float()
Options = [Option]
Option = {decimals, Decimals::0..249} |
{scientific, Decimals::0..249} |
compact
Returns a string which corresponds to the text representation of
a `Float` formatted using given options.
When decimals option is specified the returned value will contain
at most `Decimals` number of digits past the decimal point.
When `compact` option is provided the trailing zeros at the end
of the list are truncated (this option is only meaningful together
with the `decimals` option). When `scientific` option is provided,
the float will be formatted using scientific notation with
`Decimals` digits of precision. If `Options` is `[]` the function
behaves like `float_to_list/1`. When using `decimals` option and
the number doesn't fit in the static internal buffer of 256 bytes
the function throws `badarg`.
Diffstat (limited to 'erts/emulator')
-rw-r--r-- | erts/emulator/beam/atom.names | 3 | ||||
-rw-r--r-- | erts/emulator/beam/bif.c | 68 | ||||
-rw-r--r-- | erts/emulator/beam/bif.tab | 1 | ||||
-rw-r--r-- | erts/emulator/beam/sys.h | 3 | ||||
-rw-r--r-- | erts/emulator/sys/common/erl_sys_common_misc.c | 151 | ||||
-rw-r--r-- | erts/emulator/sys/unix/sys_float.c | 9 | ||||
-rw-r--r-- | erts/emulator/sys/win32/sys_float.c | 9 | ||||
-rw-r--r-- | erts/emulator/test/num_bif_SUITE.erl | 45 |
8 files changed, 274 insertions, 15 deletions
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index c47a608215..b74e2785e5 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -144,6 +144,7 @@ atom close atom closed atom code atom command +atom compact atom compat_rel atom compile atom compressed @@ -165,6 +166,7 @@ atom current_location atom current_stacktrace atom data atom debug_flags +atom decimals atom delay_trap atom dexit atom depth @@ -480,6 +482,7 @@ atom scheduler atom scheduler_id atom schedulers_online atom scheme +atom scientific atom scope atom sensitive atom sequential_tracer diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 97c8114437..182129cd36 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -2917,7 +2917,73 @@ BIF_RETTYPE float_to_list_1(BIF_ALIST_1) need = i*2; hp = HAlloc(BIF_P, need); BIF_RET(buf_to_intlist(&hp, fbuf, i, NIL)); - } +} + +BIF_RETTYPE float_to_list_2(BIF_ALIST_2) +{ + const static int arity_two = make_arityval(2); + int decimals = SYS_DEFAULT_FLOAT_DECIMALS; + int compact = 0; + enum fmt_type_ { + FMT_LEGACY, + FMT_FIXED, + FMT_SCIENTIFIC + } fmt_type = FMT_LEGACY; + Eterm list = BIF_ARG_2; + Eterm arg; + int i; + Uint need; + Eterm* hp; + FloatDef f; + char fbuf[256]; + + /* check the arguments */ + if (is_not_float(BIF_ARG_1)) + goto badarg; + + for(; is_list(list); list = CDR(list_val(list))) { + arg = CAR(list_val(list)); + if (arg == am_compact) { + compact = 1; + continue; + } else if (is_tuple(arg)) { + Eterm* tp = tuple_val(arg); + if (*tp == arity_two && is_small(tp[2])) { + decimals = signed_val(tp[2]); + if (decimals > 0 && decimals < sizeof(fbuf) - 6 /* "X." ++ "e+YY" */) + switch (tp[1]) { + case am_decimals: + fmt_type = FMT_FIXED; + continue; + case am_scientific: + fmt_type = FMT_SCIENTIFIC; + continue; + } + } + } + goto badarg; + } + if (is_not_nil(list)) { + goto badarg; + } + + GET_DOUBLE(BIF_ARG_1, f); + + if (fmt_type == FMT_FIXED) { + if ((i = sys_double_to_chars_fast(f.fd, fbuf, sizeof(fbuf), + decimals, compact)) <= 0) + goto badarg; + } else { + if ((i = sys_double_to_chars_ext(f.fd, fbuf, sizeof(fbuf), decimals)) <= 0) + goto badarg; + } + + need = i*2; + hp = HAlloc(BIF_P, need); + BIF_RET(buf_to_intlist(&hp, fbuf, i, NIL)); +badarg: + BIF_ERROR(BIF_P, BADARG); +} /**********************************************************************/ diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 59a91cd40c..a79feb6da3 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -64,6 +64,7 @@ bif erlang:external_size/1 bif erlang:external_size/2 ubif erlang:float/1 bif erlang:float_to_list/1 +bif erlang:float_to_list/2 bif erlang:fun_info/2 bif erlang:garbage_collect/0 bif erlang:garbage_collect/1 diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index 898a30b010..cecaff54a4 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -729,9 +729,12 @@ char * getenv_string(GETENV_STATE *); void fini_getenv_state(GETENV_STATE *); /* xxxP */ +#define SYS_DEFAULT_FLOAT_DECIMALS 20 void init_sys_float(void); int sys_chars_to_double(char*, double*); int sys_double_to_chars(double, char*, size_t); +int sys_double_to_chars_ext(double, char*, size_t, size_t); +int sys_double_to_chars_fast(double, char*, int, int, int); void sys_get_pid(char *, size_t); /* erts_sys_putenv() returns, 0 on success and a value != 0 on failure. */ diff --git a/erts/emulator/sys/common/erl_sys_common_misc.c b/erts/emulator/sys/common/erl_sys_common_misc.c index 461e763f03..d22914acea 100644 --- a/erts/emulator/sys/common/erl_sys_common_misc.c +++ b/erts/emulator/sys/common/erl_sys_common_misc.c @@ -105,3 +105,154 @@ int erts_get_native_filename_encoding(void) { return filename_encoding; } + +/* For internal use by sys_double_to_chars_fast() */ +static char* float_first_trailing_zero(char* p) +{ + for (--p; *p == '0' && *(p-1) == '0'; --p); + if (*(p-1) == '.') ++p; + return p; +} + +int +sys_double_to_chars(double fp, char *buffer, size_t buffer_size) +{ + return sys_double_to_chars_ext(fp, buffer, buffer_size, SYS_DEFAULT_FLOAT_DECIMALS); +} + +int +sys_double_to_chars_fast(double f, char *outbuf, int maxlen, int decimals, int compact) +{ + enum { + FRAC_SIZE = 52 + , EXP_SIZE = 11 + , EXP_MASK = (1ll << EXP_SIZE) - 1 + , FRAC_MASK = (1ll << FRAC_SIZE) - 1 + , FRAC_MASK2 = (1ll << (FRAC_SIZE + 1)) - 1 + , MAX_FLOAT = 1ll << (FRAC_SIZE+1) + }; + + long long mantissa, int_part, int_part2, frac_part; + short exp; + int sign, i, n, m, max; + double absf; + union { long long L; double F; } x; + char c, *p = outbuf; + int digit, roundup; + + x.F = f; + + exp = (x.L >> FRAC_SIZE) & EXP_MASK; + mantissa = x.L & FRAC_MASK; + sign = x.L >= 0 ? 1 : -1; + if (exp == EXP_MASK) { + if (mantissa == 0) { + if (sign == -1) + *p++ = '-'; + *p++ = 'i'; + *p++ = 'n'; + *p++ = 'f'; + } else { + *p++ = 'n'; + *p++ = 'a'; + *p++ = 'n'; + } + *p = '\0'; + return p - outbuf; + } + + exp -= EXP_MASK >> 1; + mantissa |= (1ll << FRAC_SIZE); + frac_part = 0; + int_part = 0; + absf = f * sign; + + /* Don't bother with optimizing too large numbers and decimals */ + if (absf > MAX_FLOAT || decimals > maxlen-17) { + int len = erts_snprintf(outbuf, maxlen, "%.*f", decimals, f); + if (len >= maxlen) + return -1; + p = outbuf + len; + /* Delete trailing zeroes */ + if (compact) + p = float_first_trailing_zero(outbuf + len); + *p = '\0'; + return p - outbuf; + } + + if (exp >= FRAC_SIZE) + int_part = mantissa << (exp - FRAC_SIZE); + else if (exp >= 0) { + int_part = mantissa >> (FRAC_SIZE - exp); + frac_part = (mantissa << (exp + 1)) & FRAC_MASK2; + } + else /* if (exp < 0) */ + frac_part = (mantissa & FRAC_MASK2) >> -(exp + 1); + + if (int_part == 0) { + if (sign == -1) + *p++ = '-'; + *p++ = '0'; + } else { + int ret; + while (int_part != 0) { + int_part2 = int_part / 10; + *p++ = (char)(int_part - ((int_part2 << 3) + (int_part2 << 1)) + '0'); + int_part = int_part2; + } + if (sign == -1) + *p++ = '-'; + /* Reverse string */ + ret = p - outbuf; + for (i = 0, n = ret/2; i < n; i++) { + int j = ret - i - 1; + c = outbuf[i]; + outbuf[i] = outbuf[j]; + outbuf[j] = c; + } + } + if (decimals != 0) + *p++ = '.'; + + max = maxlen - (p - outbuf) - 1 /* leave room for trailing '\0' */; + if (max > decimals) + max = decimals; + for (m = 0; m < max; m++) { + /* frac_part *= 10; */ + frac_part = (frac_part << 3) + (frac_part << 1); + + *p++ = (char)((frac_part >> (FRAC_SIZE + 1)) + '0'); + frac_part &= FRAC_MASK2; + } + + roundup = 0; + /* Rounding - look at the next digit */ + frac_part = (frac_part << 3) + (frac_part << 1); + digit = (frac_part >> (FRAC_SIZE + 1)); + if (digit > 5) + roundup = 1; + else if (digit == 5) { + frac_part &= FRAC_MASK2; + if (frac_part != 0) roundup = 1; + } + if (roundup) { + char d; + int pos = p - outbuf - 1; + do { + d = outbuf[pos]; + if (d == '-') break; + if (d == '.') continue; + if (++d != ':') { + outbuf[pos] = d; + break; + } + outbuf[pos] = '0'; + } while (--pos); + } + + /* Delete trailing zeroes */ + if (compact && *(p - 1) == '0') + p = float_first_trailing_zero(--p); + *p = '\0'; + return p - outbuf; +} diff --git a/erts/emulator/sys/unix/sys_float.c b/erts/emulator/sys/unix/sys_float.c index 3fcb4d88dc..6875c17a75 100644 --- a/erts/emulator/sys/unix/sys_float.c +++ b/erts/emulator/sys/unix/sys_float.c @@ -735,7 +735,7 @@ void erts_sys_unblock_fpe(int unmasked) /* ** Convert a double to ascii format 0.dddde[+|-]ddd - ** return number of characters converted + ** return number of characters converted or -1 if error. ** ** These two functions should maybe use localeconv() to pick up ** the current radix character, but since it is uncertain how @@ -745,11 +745,12 @@ void erts_sys_unblock_fpe(int unmasked) */ int -sys_double_to_chars(double fp, char *buffer, size_t buffer_size) +sys_double_to_chars_ext(double fp, char *buffer, size_t buffer_size, size_t decimals) { char *s = buffer; - - (void) erts_snprintf(buffer, buffer_size, "%.20e", fp); + + if (erts_snprintf(buffer, buffer_size, "%.*e", decimals, fp) >= buffer_size) + return -1; /* Search upto decimal point */ if (*s == '+' || *s == '-') s++; while (ISDIGIT(*s)) s++; diff --git a/erts/emulator/sys/win32/sys_float.c b/erts/emulator/sys/win32/sys_float.c index 09dad89140..960edaa7a5 100644 --- a/erts/emulator/sys/win32/sys_float.c +++ b/erts/emulator/sys/win32/sys_float.c @@ -114,15 +114,16 @@ sys_chars_to_double(char *buf, double *fp) /* ** Convert a double to ascii format 0.dddde[+|-]ddd -** return number of characters converted +** return number of characters converted or -1 if error. */ int -sys_double_to_chars(double fp, char *buffer, size_t buffer_size) +sys_double_to_chars_ext(double fp, char *buffer, size_t buffer_size, size_t decimals) { char *s = buffer; - - (void) erts_snprintf(buffer, buffer_size, "%.20e", fp); + + if (erts_snprintf(buffer, buffer_size, "%.*e", decimals, fp) >= buffer_size) + return -1; /* Search upto decimal point */ if (*s == '+' || *s == '-') s++; while (isdigit(*s)) s++; diff --git a/erts/emulator/test/num_bif_SUITE.erl b/erts/emulator/test/num_bif_SUITE.erl index 4459732257..7a045484cf 100644 --- a/erts/emulator/test/num_bif_SUITE.erl +++ b/erts/emulator/test/num_bif_SUITE.erl @@ -25,6 +25,7 @@ %% abs/1 %% float/1 %% float_to_list/1 +%% float_to_list/2 %% integer_to_list/1 %% list_to_float/1 %% list_to_integer/1 @@ -114,14 +115,46 @@ t_float(Config) when is_list(Config) -> ok. -%% Tests float_to_list/1. +%% Tests float_to_list/1, float_to_list/2. t_float_to_list(Config) when is_list(Config) -> - ?line test_ftl("0.0e+0", 0.0), - ?line test_ftl("2.5e+1", 25.0), - ?line test_ftl("2.5e+0", 2.5), - ?line test_ftl("2.5e-1", 0.25), - ?line test_ftl("-3.5e+17", -350.0e15), + test_ftl("0.0e+0", 0.0), + test_ftl("2.5e+1", 25.0), + test_ftl("2.5e+0", 2.5), + test_ftl("2.5e-1", 0.25), + test_ftl("-3.5e+17", -350.0e15), + "1.00000000000000000000e+00" = float_to_list(1.0), + "1.00000000000000000000e+00" = float_to_list(1.0, []), + "-1.00000000000000000000e+00" = float_to_list(-1.0, []), + "-1.00000000000000000000" = float_to_list(-1.0, [{decimals, 20}]), + {'EXIT', {badarg, _}} = (catch float_to_list(1.0, [{decimals, -1}])), + {'EXIT', {badarg, _}} = (catch float_to_list(1.0, [{decimals, 250}])), + {'EXIT', {badarg, _}} = (catch float_to_list(1.0e+300, [{decimals, 1}])), + "1.0e+300" = float_to_list(1.0e+300, [{scientific, 1}]), + "1.0" = float_to_list(1.0, [{decimals, 249}, compact]), + Expected = "1." ++ string:copies("0", 249) ++ "e+00", + Expected = float_to_list(1.0, [{scientific, 249}, compact]), + + X1 = float_to_list(1.0), + X2 = float_to_list(1.0, [{scientific, 20}]), + X1 = X2, + "1.000e+00" = float_to_list(1.0, [{scientific, 3}]), + "1.000" = float_to_list(1.0, [{decimals, 3}]), + "1.0" = float_to_list(1.0, [{decimals, 3}, compact]), + "1.12" = float_to_list(1.123, [{decimals, 2}]), + "1.123" = float_to_list(1.123, [{decimals, 3}]), + "1.123" = float_to_list(1.123, [{decimals, 3}, compact]), + "1.1230" = float_to_list(1.123, [{decimals, 4}]), + "1.12300" = float_to_list(1.123, [{decimals, 5}]), + "1.123" = float_to_list(1.123, [{decimals, 5}, compact]), + "1.1234" = float_to_list(1.1234,[{decimals, 6}, compact]), + "2.333333" = erlang:float_to_list(7/3, [{decimals, 6}, compact]), + "2.333333" = erlang:float_to_list(7/3, [{decimals, 6}]), + "0.00000000000000000000e+00" = float_to_list(0.0, [compact]), + "0.0" = float_to_list(0.0, [{decimals, 10}, compact]), + "123000000000000000000.0" = float_to_list(1.23e20, [{decimals, 10}, compact]), + "1.2300000000e+20" = float_to_list(1.23e20, [{scientific, 10}, compact]), + "1.23000000000000000000e+20" = float_to_list(1.23e20, []), ok. test_ftl(Expect, Float) -> |