diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 028e2bcafcdc..b908f2b94fbc 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -1184,6 +1184,7 @@ RUN_OBJS += \ $(OBJDIR)/erl_guard_bifs.o $(OBJDIR)/erl_dirty_bif_wrap.o \ $(OBJDIR)/erl_trace.o $(OBJDIR)/copy.o \ $(OBJDIR)/utils.o $(OBJDIR)/bif.o \ + $(OBJDIR)/erl_based_float.o \ $(OBJDIR)/io.o $(OBJDIR)/erl_printf_term.o\ $(OBJDIR)/erl_debug.o $(OBJDIR)/erl_debugger.o \ $(OBJDIR)/erl_message.o $(OBJDIR)/erl_proc_sig_queue.o \ diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 2f943195f110..0c5ebedc407e 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -130,6 +130,7 @@ atom badopt badtype atom bad_map_iterator atom bag atom band +atom base atom big atom bif_handle_signals_return atom bif_return_trap diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 671e6f2d07f1..8cb96597c9c9 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -57,6 +57,7 @@ #endif #include "jit/beam_asm.h" #include "erl_global_literals.h" +#include "erl_based_float.h" #include "beam_load.h" #include "beam_common.h" #include "dtrace-wrapper.h" @@ -3365,53 +3366,11 @@ BIF_RETTYPE integer_to_list_2(BIF_ALIST_2) /**********************************************************************/ -static int do_float_to_charbuf(Process *p, Eterm efloat, Eterm list, - char *fbuf, int sizeof_fbuf) { - - Eterm arity_two = make_arityval(2); - int decimals = SYS_DEFAULT_FLOAT_DECIMALS; - int compact = 0; - enum fmt_type_ { - FMT_LEGACY, - FMT_SHORT, - FMT_FIXED, - FMT_SCIENTIFIC - } fmt_type = FMT_LEGACY; - Eterm arg; - FloatDef f; - - /* check the arguments */ - if (is_not_float(efloat)) - goto badarg; +static int do_float_to_charbuf(Process *p, Eterm efloat, struct erl_float_opts *opts, + char *fbuf, int sizeof_fbuf) { - 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]); - switch (tp[1]) { - case am_decimals: - fmt_type = FMT_FIXED; - continue; - case am_scientific: - fmt_type = FMT_SCIENTIFIC; - continue; - } - } - } else if (arg == am_short) { - fmt_type = FMT_SHORT; - continue; - } - goto badarg; - } - - if (is_not_nil(list)) { - goto badarg; - } + FloatDef f; + enum erl_fmt_type fmt_type = opts->fmt_type; GET_DOUBLE(efloat, f); @@ -3427,61 +3386,148 @@ static int do_float_to_charbuf(Process *p, Eterm efloat, Eterm list, #endif } else if (fmt_type == FMT_FIXED) { return sys_double_to_chars_fast(f.fd, fbuf, sizeof_fbuf, - decimals, compact); + opts->decimals, opts->compact); } else { - return sys_double_to_chars_ext(f.fd, fbuf, sizeof_fbuf, decimals); + return sys_double_to_chars_ext(f.fd, fbuf, sizeof_fbuf, opts->decimals); } +} -badarg: - return -1; +static Eterm check_float_args(Process *c_p, Eterm Float, Eterm Opts, struct erl_float_opts *opts) +{ + const Eterm arity_two = make_arityval(2); + opts->base = 10; + opts->fmt_type = FMT_LEGACY; + opts->compact = false; + opts->decimals = SYS_DEFAULT_FLOAT_DECIMALS; + + if (is_not_float(Float)) { + goto badarg; + } + + for (; is_list(Opts); Opts = CDR(list_val(Opts))) { + Eterm arg = CAR(list_val(Opts)); + if (arg == am_compact) { + opts->compact = true; + continue; + } else if (is_tuple(arg)) { + Eterm* tp = tuple_val(arg); + if (*tp == arity_two && is_small(tp[2])) { + Sint opt_arg = signed_val(tp[2]); + + if (tp[1] == am_base) { + if (opt_arg < 2 || opt_arg > 36) { + goto badarg; + } + opts->base = opt_arg; + continue; + } else if (tp[1] == am_decimals) { + if (opt_arg < 0 || opt_arg > 253) { + goto badarg; + } + opts->fmt_type = FMT_FIXED; + opts->decimals = opt_arg; + continue; + } else if (tp[1] == am_scientific) { + if (opt_arg < 0 || opt_arg > 249) { + goto badarg; + } + opts->fmt_type = FMT_SCIENTIFIC; + opts->decimals = opt_arg; + continue; + } + } + } else if (arg == am_short) { + opts->fmt_type = FMT_SHORT; + continue; + } + goto badarg; + } + + if (is_not_nil(Opts)) { + badarg: + BIF_ERROR(c_p, BADARG); + } + + return am_true; } /* convert a float to a list of ascii characters */ -static BIF_RETTYPE do_float_to_list(Process *BIF_P, Eterm arg, Eterm opts) { - int used; - Eterm* hp; - char fbuf[256]; - - if ((used = do_float_to_charbuf(BIF_P,arg,opts,fbuf,sizeof(fbuf))) <= 0) { - BIF_ERROR(BIF_P, BADARG); - } - hp = HAlloc(BIF_P, (Uint)used*2); - BIF_RET(buf_to_intlist(&hp, fbuf, (Uint)used, NIL)); +static BIF_RETTYPE do_float_to_list(Process *BIF_P, Eterm arg, struct erl_float_opts *opts) { + int used; + Eterm* hp; + char fbuf[256]; + + if ((used = do_float_to_charbuf(BIF_P, arg, opts, fbuf, sizeof(fbuf))) <= 0) { + BIF_ERROR(BIF_P, BADARG); + } + hp = HAlloc(BIF_P, (Uint)used*2); + BIF_RET(buf_to_intlist(&hp, fbuf, (Uint)used, NIL)); } BIF_RETTYPE float_to_list_1(BIF_ALIST_1) { - return do_float_to_list(BIF_P,BIF_ARG_1,NIL); + struct erl_float_opts opts; + + check_float_args(BIF_P, BIF_ARG_1, NIL, &opts); + return do_float_to_list(BIF_P, BIF_ARG_1, &opts); } BIF_RETTYPE float_to_list_2(BIF_ALIST_2) { - return do_float_to_list(BIF_P,BIF_ARG_1,BIF_ARG_2); + struct erl_float_opts opts; + Eterm res; + + res = check_float_args(BIF_P, BIF_ARG_1, BIF_ARG_2, &opts); + if (is_non_value(res)) { + BIF_RET(res); + } + + if (opts.base != 10) { + return erl_based_float_to_list(BIF_P, BIF_ARG_1, &opts); + } else { + return do_float_to_list(BIF_P, BIF_ARG_1, &opts); + } } + /* convert a float to a binary of ascii characters */ -static BIF_RETTYPE do_float_to_binary(Process *BIF_P, Eterm arg, Eterm opts) { - char fbuf[256]; - int used; +static BIF_RETTYPE do_float_to_binary(Process *BIF_P, Eterm arg, struct erl_float_opts *opts) { + char fbuf[256]; + int used; - if ((used = do_float_to_charbuf(BIF_P,arg,opts,fbuf,sizeof(fbuf))) <= 0) { - BIF_ERROR(BIF_P, BADARG); - } + if ((used = do_float_to_charbuf(BIF_P, arg, opts, fbuf, sizeof(fbuf))) <= 0) { + BIF_ERROR(BIF_P, BADARG); + } - BIF_RET(erts_new_binary_from_data(BIF_P, (Uint)used, (byte*)fbuf)); + BIF_RET(erts_new_binary_from_data(BIF_P, (Uint)used, (byte*)fbuf)); } BIF_RETTYPE float_to_binary_1(BIF_ALIST_1) { - return do_float_to_binary(BIF_P,BIF_ARG_1,NIL); + struct erl_float_opts opts; + + check_float_args(BIF_P, BIF_ARG_1, NIL, &opts); + return do_float_to_binary(BIF_P, BIF_ARG_1, &opts); } BIF_RETTYPE float_to_binary_2(BIF_ALIST_2) { - return do_float_to_binary(BIF_P,BIF_ARG_1,BIF_ARG_2); + struct erl_float_opts opts; + Eterm res; + + res = check_float_args(BIF_P, BIF_ARG_1, BIF_ARG_2, &opts); + if (is_non_value(res)) { + BIF_RET(res); + } + + if (opts.base != 10) { + return erl_based_float_to_binary(BIF_P, BIF_ARG_1, &opts); + } else { + return do_float_to_binary(BIF_P, BIF_ARG_1, &opts); + } } /**********************************************************************/ @@ -3671,7 +3717,7 @@ BIF_RETTYPE string_list_to_float_1(BIF_ALIST_1) BIF_RET(tup); } -static BIF_RETTYPE do_charbuf_to_float(Process *BIF_P,char *buf) { +BIF_RETTYPE do_charbuf_to_float(Process *BIF_P,char *buf) { FloatDef f; Eterm res; Eterm* hp; diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index a4b1aa58ed66..bcd679891f80 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -859,3 +859,9 @@ bif records:is_exported/1 bif records:create/4 bif records:update/4 bif records:get_definition/2 + +# +# New in 30. +# +bif erlang:binary_to_float/2 +bif erlang:list_to_float/2 diff --git a/erts/emulator/beam/erl_based_float.c b/erts/emulator/beam/erl_based_float.c new file mode 100644 index 000000000000..95d37dc023f0 --- /dev/null +++ b/erts/emulator/beam/erl_based_float.c @@ -0,0 +1,630 @@ +/* + * %CopyrightBegin% + * + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright Ericsson AB 2026. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +/* + * For based float conversion: + * BIFs: list_to_float/2, binary_to_float/2 + * Helper functions for float_to_list/2, float_to_binary/2 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "sys.h" +#include "erl_vm.h" +#include "global.h" +#include "bif.h" +#include "erl_binary.h" +#include "erl_based_float.h" + +/* Convert a char to its numeric value in the given base. + * Return -1 if the value is invalid in the given base. + */ +static int digit_val(char c, int base) +{ + int d; + if (c >= '0' && c <= '9') + d = c - '0'; + else if (c >= 'a' && c <= 'z') + d = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') + d = c - 'A' + 10; + else + return -1; + return (d < base) ? d : -1; +} + +/* + * Given a string representation of a float and its base, + * convert it into a C double. + * The string must contain a dot, can optionally contain a sign + * and an exponent. + * Return true on success and false on failure. + */ +static bool based_charbuf_to_float(char *buf, int len, int base, double *result) +{ + int i = 0; + int negative = 0; + int d; + int frac_len = 0; + int ndigits = 0; + int exp = 0; + double N = 0.0; + double F = 0.0; + + /* Optional sign */ + if (buf[i] == '-') { + negative = 1; + i++; + } else if (buf[i] == '+') { + i++; + } + + /* Integer part. Skip underscore. Stop at dot. */ + while (i < len && buf[i] != '.') { + if (buf[i] == '_') { + i++; + continue; + } + d = digit_val(buf[i], base); + if (d < 0) + return false; + N = N * base + d; + i++, ndigits++; + } + + if (!erts_isfinite(N)) + return false; + + /* Error if no dot */ + if (i >= len || buf[i] != '.') + return false; + i++; + + /* Fractional part. Skip underscore. Stop at '#' or end */ + while (i < len && buf[i] != '#') { + if (buf[i] == '_') { + i++; + continue; + } + d = digit_val(buf[i], base); + if (d < 0) + return false; + if (frac_len < 60) { + F = F * base + d; + frac_len++; + } + i++; + } + + /* Optional exponent: #e±decimal or #E±decimal */ + if (i < len && buf[i] == '#') { + int negative_exp = 0; + i++; + if (i >= len || (buf[i] != 'e' && buf[i] != 'E')) + return false; + i++; + if (i < len && buf[i] == '+') { + i++; + } else if (i < len && buf[i] == '-') { + negative_exp = 1; + i++; + } + if (i >= len || buf[i] < '0' || buf[i] > '9') + return false; + while (i < len) { + if (buf[i] == '_') { + i++; + continue; + } + if (buf[i] < '0' || buf[i] > '9') + return false; + exp = exp * 10 + (buf[i] - '0'); + i++; + } + if (negative_exp) exp = -exp; + } + + + if (i != len) + return false; + + /* Convert to base 10 using the same algorithm as erl_scan: + * Collect all digits to create an integer N, then scale according + * to dot position, exponent, and sign. + */ + { + double int_part; + double frac_part; + double val; + + int_part = N * pow((double)base, (double)exp); + frac_part = F * pow((double)base, (double)exp-frac_len); + val = int_part + frac_part; + + if (negative) + val = -val; + + /* Reject infinity or NaN */ + if (!erts_isfinite(val)) + return false; + + *result = val; + } + + return true; +} + +/* + * Convert a C double into an Erlang float and return as an Eterm. + */ +static Eterm make_float_term(Process *c_p, double val) +{ + FloatDef f; + Eterm res; + Eterm* hp; + f.fd = val; + hp = HAlloc(c_p, FLOAT_SIZE_OBJECT); + res = make_float(hp); + PUT_DOUBLE(f, hp); + return res; +} + +static int do_based_float_to_charbuf(Eterm efloat, struct erl_float_opts *opts, + char *fbuf, int sizeof_fbuf) +{ + FloatDef f; + double num, frac; + + enum erl_fmt_type fmt_type = opts->fmt_type; + int base = opts->base; + int decimals = opts->decimals; + bool compact = opts->compact; + + char *p = fbuf; + char int_digits[128]; + int int_len = 0; + int i, d; + + GET_DOUBLE(efloat, f); + + if (!erts_isfinite(f.fd)) + return -1; + + if (signbit(f.fd)) { + *p++ = '-'; + f.fd = -f.fd; + } + + frac = modf(f.fd, &num); + if (fmt_type == FMT_LEGACY || fmt_type == FMT_SCIENTIFIC) { + /* One digit before dot */ + int exp = 0; + double val = num + frac; + + if (val >= (double)base) { + while (val >= (double)base) { + val /= (double)base; + exp++; + } + } else if (val > 0.0 && val < 1.0) { + while (val < 1.0) { + val *= (double)base; + exp--; + } + } + + d = (int)val; + if (d >= base) d = base - 1; + *p++ = (d < 10) ? '0' + d : 'a' + d - 10; + val -= d; + + *p++ = '.'; + for (i = 0; i < decimals; i++) { + val *= base; + d = (int)val; + if (d >= base) d = base - 1; + *p++ = (d < 10) ? '0' + d : 'a' + d - 10; + val -= d; + } + + if (compact) { + while (p > fbuf + 2 && *(p - 1) == '0' && *(p - 2) != '.') + p--; + } + + /* Exponent in decimal */ + *p++ = '#'; + *p++ = 'e'; + if (exp < 0) { + *p++ = '-'; + exp = -exp; + } + { + char exp_buf[16]; + int elen = 0; + if (exp == 0) { + exp_buf[elen++] = '0'; + } else { + while (exp > 0) { + exp_buf[elen++] = '0' + (exp % 10); + exp /= 10; + } + } + for (i = elen - 1; i >= 0; i--) + *p++ = exp_buf[i]; + } + } else if (fmt_type == FMT_FIXED) { + /* Normal decimal format */ + if (num == 0.0) { + int_digits[int_len++] = '0'; + } else { + unsigned long ip = (unsigned long)num; + while (ip > 0) { + d = ip % base; + int_digits[int_len++] = (d < 10) ? '0' + d : 'a' + d - 10; + ip /= base; + } + } + for (i = int_len - 1; i >= 0; i--) + *p++ = int_digits[i]; + + *p++ = '.'; + for (i = 0; i < decimals; i++) { + frac *= base; + d = (int)frac; + if (d >= base) d = base - 1; + *p++ = (d < 10) ? '0' + d : 'a' + d - 10; + frac -= d; + } + + if (compact) { + while (p > fbuf + 2 && *(p - 1) == '0' && *(p - 2) != '.') + p--; + } + } else if (fmt_type == FMT_SHORT) { + double val = num + frac; + int max_digits; + int exp = 0; + char trial[256]; + int trial_len; + double roundtrip; + + if (val == 0.0) { + *p++ = '0'; + *p++ = '.'; + *p++ = '0'; + return (int)(p - fbuf); + } + + /* Max significant digits: ceil(53 / log2(base)) */ + { + static const int max_digits_table[] = { + 0, 0, /* invalid bases: 0, 1 */ + 54, /* 2 */ + 35, 28, 24, 22, 20, 19, 18, /* 3-9 */ + 17, 17, 16, 16, 15, 15, 15, /* 10-16 */ + 14, 14, 14, 14, 13, 13, 13, /* 17-23 */ + 13, 13, 13, 12, 12, 12, 12, /* 24-30 */ + 12, 12, 12, 12, 12, 12 /* 31-36 */ + }; + max_digits = max_digits_table[base]; + } + + /* Convert to scientific */ + if (val >= (double)base) { + while (val >= (double)base) { + val /= (double)base; + exp++; + } + } else if (val > 0.0 && val < 1.0) { + while (val < 1.0) { + val *= (double)base; + exp--; + } + } + + /* Generate max_digits significant digits */ + { + char sci_buf[256]; + char *sp = sci_buf; + double sv = val; + int d, i; + int sig_digits; + int use_scientific; + + d = (int)sv; + if (d >= base) d = base - 1; + *sp++ = (d < 10) ? '0' + d : 'a' + d - 10; + sv -= d; + + *sp++ = '.'; + for (i = 1; i < max_digits; i++) { + sv *= base; + d = (int)sv; + if (d >= base) d = base - 1; + *sp++ = (d < 10) ? '0' + d : 'a' + d - 10; + sv -= d; + } + + /* Trim digits to find shortest round-tripping representation */ + sig_digits = max_digits; + while (sig_digits > 1) { + /* Build trial with (sig_digits - 1) significant digits */ + char *tp = trial; + int ti; + + trial[0] = sci_buf[0]; + tp = trial + 1; + *tp++ = '.'; + + /* fractional digits: at most (sig_digits - 2) */ + for (ti = 0; ti < sig_digits - 2; ti++) { + *tp++ = sci_buf[2 + ti]; + } + if (sig_digits - 2 <= 0) + *tp++ = '0'; /* need at least one digit after dot */ + + /* exponent */ + *tp++ = '#'; + *tp++ = 'e'; + if (exp < 0) { + *tp++ = '-'; + ti = -exp; + } else { + ti = exp; + } + if (ti == 0) { + *tp++ = '0'; + } else { + char ebuf[16]; + int elen = 0; + while (ti > 0) { + ebuf[elen++] = '0' + (ti % 10); + ti /= 10; + } + for (ti = elen - 1; ti >= 0; ti--) + *tp++ = ebuf[ti]; + } + trial_len = (int)(tp - trial); + + /* Round-trip test: try one fewer digit */ + if (based_charbuf_to_float(trial, trial_len, base, &roundtrip) + && roundtrip == (num + frac)) { + sig_digits--; + } else { + break; + } + } + + + if (sig_digits < 1) sig_digits = 1; + + /* Decide between scientific or normal format */ + use_scientific = 1; + + if ((num + frac) < 9007199254740992.0) { /* < 2^53 */ + /* Compare lengths of fixed vs scientific */ + int sci_len, fix_len; + int fix_int_digits; + + /* Scientific length = sig_digits + 1(dot) + 2(#e) + exp_digits */ + sci_len = sig_digits + 1 + 2; + { + int e = (exp >= 0) ? exp : -exp; + if (e == 0) sci_len += 1; + else { while (e > 0) { sci_len++; e /= 10; } } + if (exp < 0) sci_len++; + } + + /* Normal length = int_digits + 1(dot) + frac_digits */ + if (exp >= 0) { + int fix_frac_digits; + fix_int_digits = exp + 1; + fix_frac_digits = sig_digits - fix_int_digits; + if (fix_frac_digits < 1) fix_frac_digits = 1; + fix_len = fix_int_digits + 1 + fix_frac_digits; + } else { + /* 0.000...digits */ + int leading_zeros; + fix_int_digits = 1; /* "0" */ + leading_zeros = -exp - 1; + fix_len = 1 + 1 + leading_zeros + sig_digits; /* 0 . zeros digits */ + } + + if (fix_len <= sci_len) + use_scientific = 0; + } + + /* Write output */ + if (use_scientific) { + *p++ = sci_buf[0]; + *p++ = '.'; + for (i = 0; i < sig_digits - 1; i++) + *p++ = sci_buf[2 + i]; + if (sig_digits == 1) + *p++ = '0'; /* at least one digit after dot */ + *p++ = '#'; + *p++ = 'e'; + if (exp < 0) { + *p++ = '-'; + exp = -exp; + } + if (exp == 0) { + *p++ = '0'; + } else { + char ebuf[16]; + int elen = 0; + while (exp > 0) { + ebuf[elen++] = '0' + (exp % 10); + exp /= 10; + } + for (i = elen - 1; i >= 0; i--) + *p++ = ebuf[i]; + } + } else { + /* Normal decimal */ + if (exp >= 0) { + int written; + /* Integer part: first (exp+1) significant digits */ + *p++ = sci_buf[0]; + for (i = 0; i < exp && i < sig_digits - 1; i++) + *p++ = sci_buf[2 + i]; + /* Pad with zeros if needed */ + for (; i < exp; i++) + *p++ = '0'; + *p++ = '.'; + /* Fractional part: remaining significant digits */ + written = 0; + for (; i < sig_digits - 1; i++) { + *p++ = sci_buf[2 + i]; + written++; + } + if (written == 0) + *p++ = '0'; /* at least one digit after dot */ + } else { + /* Value < 1: "0." + 0s + significant digits */ + *p++ = '0'; + *p++ = '.'; + for (i = 0; i < -exp - 1; i++) + *p++ = '0'; + *p++ = sci_buf[0]; + for (i = 0; i < sig_digits - 1; i++) + *p++ = sci_buf[2 + i]; + } + } + } + + return (int)(p - fbuf); + } + + return (int)(p - fbuf); +} + +BIF_RETTYPE erl_based_float_to_list(Process *BIF_P, Eterm efloat, + struct erl_float_opts *opts) +{ + char fbuf[256]; + int used; + Eterm* hp; + + used = do_based_float_to_charbuf(efloat, opts, fbuf, sizeof(fbuf)); + if (used <= 0) + BIF_ERROR(BIF_P, BADARG); + + hp = HAlloc(BIF_P, (Uint)used * 2); + BIF_RET(buf_to_intlist(&hp, fbuf, (Uint)used, NIL)); +} + +BIF_RETTYPE erl_based_float_to_binary(Process *BIF_P, Eterm efloat, + struct erl_float_opts *opts) +{ + char fbuf[256]; + int used; + + used = do_based_float_to_charbuf(efloat, opts, fbuf, sizeof(fbuf)); + if (used <= 0) + BIF_ERROR(BIF_P, BADARG); + + BIF_RET(erts_new_binary_from_data(BIF_P, (Uint)used, (byte*)fbuf)); +} + +BIF_RETTYPE list_to_float_2(BIF_ALIST_2) +{ + Sint len; + SWord base; + char *buf = NULL; + double result; + + len = erts_list_length(BIF_ARG_1); + if (len < 0) + BIF_ERROR(BIF_P, BADARG); + + if (is_not_small(BIF_ARG_2)) + BIF_ERROR(BIF_P, BADARG); + + base = signed_val(BIF_ARG_2); + if (base < 2 || base > 36) + BIF_ERROR(BIF_P, BADARG); + + buf = (char *) erts_alloc(ERTS_ALC_T_TMP, len + 1); + + if (intlist_to_buf(BIF_ARG_1, buf, len) < 0) + goto badarg; + buf[len] = '\0'; + + if (base == 10) { + Eterm res = do_charbuf_to_float(BIF_P, buf); + erts_free(ERTS_ALC_T_TMP, (void *) buf); + BIF_RET(res); + } + + if (based_charbuf_to_float(buf, len, base, &result)) { + erts_free(ERTS_ALC_T_TMP, (void *) buf); + BIF_RET(make_float_term(BIF_P, result)); + } + +badarg: + erts_free(ERTS_ALC_T_TMP, (void *) buf); + BIF_ERROR(BIF_P, BADARG); +} + +BIF_RETTYPE binary_to_float_2(BIF_ALIST_2) +{ + SWord base; + Uint offset, size; + byte *bin_base; + + if (is_not_bitstring(BIF_ARG_1) || is_not_small(BIF_ARG_2)) + BIF_ERROR(BIF_P, BADARG); + + base = signed_val(BIF_ARG_2); + if (base < 2 || base > 36) + BIF_ERROR(BIF_P, BADARG); + + ERTS_GET_BITSTRING(BIF_ARG_1, bin_base, offset, size); + + if (size > 0 && TAIL_BITS(size) == 0) { + double result; + + if (base == 10) { + byte *char_buf; + Eterm res; + + char_buf = erts_alloc(ERTS_ALC_T_TMP, NBYTES(size) + 1); + copy_binary_to_buffer(char_buf, 0, bin_base, offset, size); + char_buf[NBYTES(size)] = '\0'; + + res = do_charbuf_to_float(BIF_P, (char*)char_buf); + erts_free(ERTS_ALC_T_TMP, (void*)char_buf); + BIF_RET(res); + } else if (based_charbuf_to_float((char*)bin_base + offset, + NBYTES(size), + base, &result)) { + BIF_RET(make_float_term(BIF_P, result)); + } + } + + BIF_ERROR(BIF_P, BADARG); +} diff --git a/erts/emulator/beam/erl_based_float.h b/erts/emulator/beam/erl_based_float.h new file mode 100644 index 000000000000..d36326d0136d --- /dev/null +++ b/erts/emulator/beam/erl_based_float.h @@ -0,0 +1,47 @@ +/* + * %CopyrightBegin% + * + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright Ericsson AB 2026. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +#ifndef ERL_BASED_FLOAT_H__ +#define ERL_BASED_FLOAT_H__ + +enum erl_fmt_type { + FMT_LEGACY, + FMT_SHORT, + FMT_FIXED, + FMT_SCIENTIFIC +}; + +struct erl_float_opts { + int base; + enum erl_fmt_type fmt_type; + bool compact; + int decimals; +}; + + +BIF_RETTYPE erl_based_float_to_list(Process *BIF_P, Eterm arg, struct erl_float_opts *opts); +BIF_RETTYPE erl_based_float_to_binary(Process *BIF_P, Eterm arg, struct erl_float_opts *opts); + +/* Defined in bif.c, used for base-10 fallback */ +BIF_RETTYPE do_charbuf_to_float(Process *BIF_P, char *buf); + +#endif /* ERL_BASED_FLOAT_H__ */ diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl index 130b4fe14dac..65d351098239 100644 --- a/erts/emulator/test/exception_SUITE.erl +++ b/erts/emulator/test/exception_SUITE.erl @@ -766,6 +766,12 @@ error_info(_Config) -> {binary_to_float, [abc]}, {binary_to_float, [<<"0">>]}, {binary_to_float, [<<"abc">>]}, + + {binary_to_float, [<<"abc">>, 2]}, + {binary_to_float, [abc, 2]}, + {binary_to_float, [<<"42.0">>, 1]}, + {binary_to_float, [<<"42.0">>, 37]}, + {binary_to_integer, [abc]}, {binary_to_integer, [<<"abc">>]}, @@ -971,6 +977,11 @@ error_info(_Config) -> {list_to_float, ["abc"]}, {list_to_float, [abc]}, + {list_to_float, ["abc", 2]}, + {list_to_float, [abc, 2]}, + {list_to_float, ["42.0", 1]}, + {list_to_float, ["42.0", 37]}, + {list_to_integer, ["abc"]}, {list_to_integer, [[a,b,c]]}, {list_to_integer, [[a|b]]}, diff --git a/erts/emulator/test/num_bif_SUITE.erl b/erts/emulator/test/num_bif_SUITE.erl index fe23be2ed45d..fb40d7002520 100644 --- a/erts/emulator/test/num_bif_SUITE.erl +++ b/erts/emulator/test/num_bif_SUITE.erl @@ -27,9 +27,11 @@ %% Tests the BIFs: %% abs/1 +%% binary_to_float/2 %% float/1 %% float_to_list/1 -%% float_to_list/2 +%% float_to_list/2 +%% float_to_binary/2 %% integer_to_list/1 %% list_to_float/1 %% list_to_integer/1 @@ -46,15 +48,16 @@ t_float_to_string/1, t_integer_to_string/1, t_string_to_integer/1, t_list_to_integer_edge_cases/1, t_string_to_float_safe/1, t_string_to_float_risky/1, - t_round/1, t_trunc_and_friends/1 + t_round/1, t_trunc_and_friends/1, + t_based_float/1, t_binary_to_float_2/1 ]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [t_abs, t_float, t_float_to_string, t_integer_to_string, - {group, t_string_to_float}, t_string_to_integer, t_round, - t_trunc_and_friends, t_list_to_integer_edge_cases]. + [t_abs, t_float, t_float_to_string, t_based_float, t_binary_to_float_2, + t_integer_to_string, {group, t_string_to_float}, t_string_to_integer, + t_round, t_trunc_and_friends, t_list_to_integer_edge_cases]. groups() -> [{t_string_to_float, [], @@ -131,24 +134,22 @@ t_float_to_string(Config) when is_list(Config) -> test_fts("1.00000000000000000000e+00",1.0, []), test_fts("-1.00000000000000000000e+00",-1.0, []), test_fts("-1.00000000000000000000",-1.0, [{decimals, 20}]), - {'EXIT', {badarg, _}} = (catch float_to_list(1.0, [{decimals, -1}])), - {'EXIT', {badarg, _}} = (catch float_to_list(1.0, [{decimals, 254}])), - {'EXIT', {badarg, _}} = (catch float_to_list(1.0, [{scientific, 250}])), - {'EXIT', {badarg, _}} = (catch float_to_list(1.0e+300, [{decimals, 1}])), - {'EXIT', {badarg, _}} = (catch float_to_binary(1.0, [{decimals, -1}])), - {'EXIT', {badarg, _}} = (catch float_to_binary(1.0, [{decimals, 254}])), - {'EXIT', {badarg, _}} = (catch float_to_binary(1.0, [{scientific, 250}])), - {'EXIT', {badarg, _}} = (catch float_to_binary(1.0e+300, [{decimals, 1}])), + ?assertError(badarg, float_to_list(1.0, [{decimals, -1}])), + ?assertError(badarg, float_to_list(1.0, [{decimals, 254}])), + ?assertError(badarg, float_to_list(1.0, [{scientific, 250}])), + ?assertError(badarg, float_to_list(1.0e+300, [{decimals, 1}])), + ?assertError(badarg, float_to_binary(1.0, [{decimals, -1}])), + ?assertError(badarg, float_to_binary(1.0, [{decimals, 254}])), + ?assertError(badarg, float_to_binary(1.0, [{scientific, 250}])), + ?assertError(badarg, float_to_binary(1.0e+300, [{decimals, 1}])), test_fts("1.0e+300",1.0e+300, [{scientific, 1}]), test_fts("1.0",1.0, [{decimals, 249}, compact]), test_fts("1",1.0,[{decimals,0}]), test_fts("2",1.9,[{decimals,0}]), test_fts("123456789012345680.0",123456789012345678.0, [{decimals, 236}, compact]), - {'EXIT', {badarg, _}} = (catch float_to_list( - 123456789012345678.0, [{decimals, 237}])), - {'EXIT', {badarg, _}} = (catch float_to_binary( - 123456789012345678.0, [{decimals, 237}])), + ?assertError(badarg, float_to_list(123456789012345678.0, [{decimals, 237}])), + ?assertError(badarg, float_to_binary(123456789012345678.0, [{decimals, 237}])), test_fts("1." ++ lists:duplicate(249, $0) ++ "e+00", 1.0, [{scientific, 249}, compact]), @@ -364,6 +365,179 @@ max_diff_decimals(F, D) -> (math:pow(10, -min(D,MaxDec)) / 2) + Resolution. +%% Tests float_to_list/2, float_to_binary/2 with {base, B} option. + +t_based_float(Config) when is_list(Config) -> + rand_seed(), + + %% Default format (scientific 20 decimals) + test_bfs("1.10101100000000000000#e3", 13.375, [{base, 2}]), + test_bfs("d.60000000000000000000#e0", 13.375, [{base, 16}]), + + %% Base 2 with decimals + test_bfs("1101.011", 13.375, [{base, 2}, {decimals, 3}]), + + %% Base 16 with decimals + test_bfs("d.6", 13.375, [{base, 16}, {decimals, 1}]), + + %% Base 8 + test_bfs("15.3", 13.375, [{base, 8}, {decimals, 1}]), + + %% Negative values + test_bfs("-1.10101100000000000000#e3", -13.375, [{base, 2}]), + test_bfs("-d.60000000000000000000#e0", -13.375, [{base, 16}]), + + %% With {decimals, N}, compact + test_bfs("1101.011", 13.375, [{base, 2}, {decimals, 10}, compact]), + test_bfs("d.6", 13.375, [{base, 16}, {decimals, 10}, compact]), + + %% With {scientific, N} + test_bfs("1.10101100000#e3", 13.375, [{base, 2}, {scientific, 11}]), + test_bfs("d.60000#e0", 13.375, [{base, 16}, {scientific, 5}]), + + %% With {scientific, N}, compact + test_bfs("1.101011#e3", 13.375, [{base, 2}, {scientific, 11}, compact]), + test_bfs("d.6#e0", 13.375, [{base, 16}, {scientific, 5}, compact]), + + %% Base 36 + test_bfs("d.di000000", 13.375, [{base, 36}, {decimals, 8}]), + + %% Base 10 should match normal behavior + test_bfs("1.000", 1.0, [{base, 10}, {decimals, 3}]), + + %% Zero + test_bfs("0.0", 0.0, [{base, 2}, {decimals, 1}]), + test_bfs("0.0", 0.0, [{base, 16}, {decimals, 1}]), + + %% Negative zero + <> = <<16#8000000000000000:64>>, + test_bfs("-0.0", NegZero, [{base, 2}, {decimals, 1}]), + test_bfs("-0.0", NegZero, [{base, 2}, {decimals, 1}, compact]), + test_bfs("-0.0#e0", NegZero, [{base, 2}, {scientific, 1}]), + test_bfs("-0.0#e0", NegZero, [{base, 2}, {scientific, 1}, compact]), + test_bfs("-0.0", NegZero, [{base, 16}, {decimals, 1}]), + test_bfs("-0.0", NegZero, [{base, 16}, {decimals, 1}, compact]), + test_bfs("-0.0#e0", NegZero, [{base, 16}, {scientific, 1}]), + test_bfs("-0.0#e0", NegZero, [{base, 16}, {scientific, 1}, compact]), + test_bfs("-0.00000000000000000000#e0", NegZero, [{base, 2}]), + test_bfs("-0.00000000000000000000#e0", NegZero, [{base, 16}]), + + %% Short option - basic + test_bfs("1101.011", 13.375, [{base, 2}, short]), + test_bfs("d.6", 13.375, [{base, 16}, short]), + test_bfs("-d.6", -13.375, [{base, 16}, short]), + + %% Short option - integer values + test_bfs("10.0", 2.0, [{base, 2}, short]), + test_bfs("a.0", 10.0, [{base, 16}, short]), + test_bfs("100.0", 4.0, [{base, 2}, short]), + + %% Short option - small fractions + test_bfs("0.1", 0.5, [{base, 2}, short]), + test_bfs("0.01", 0.25, [{base, 2}, short]), + test_bfs("0.8", 0.5, [{base, 16}, short]), + + %% Short option - zero + test_bfs("0.0", 0.0, [{base, 2}, short]), + test_bfs("0.0", 0.0, [{base, 16}, short]), + + %% Short option - negative zero + <> = <<16#8000000000000000:64>>, + test_bfs("-0.0", NegZero, [{base, 2}, short]), + test_bfs("-0.0", NegZero, [{base, 16}, short]), + + %% Short option - values near 1 + test_bfs("1.0", 1.0, [{base, 2}, short]), + test_bfs("1.0", 1.0, [{base, 16}, short]), + test_bfs("1.0", 1.0, [{base, 8}, short]), + + %% Short option - scientific notation for large values (>= 2^53) + test_bfs("1.0#e53", float(1 bsl 53), [{base, 2}, short]), + test_bfs("2.0#e13", float(1 bsl 53), [{base, 16}, short]), + + %% Short option - scientific notation for very small values + test_bfs("1.0#e-10", math:pow(2, -10), [{base, 2}, short]), + + %% Test many trailing zeroes + OneDotZero = "1.0" ++ lists:duplicate(10_000, $0), + _ = [?assertEqual(1.0, list_to_float(OneDotZero, Base)) || + Base <- lists:seq(2, 36)], + + %% Test random floats + _ = [test_rand_bfs(Base) || + _ <- lists:seq(1, 50), + Base <- lists:seq(2, 36)], + + %% Error cases + ?assertError(badarg, float_to_list(1.0, [{base, 1}])), + ?assertError(badarg, float_to_list(1.0, [{base, 37}])), + ?assertError(badarg, float_to_list(1.0, [{base, 0}])), + ?assertError(badarg, float_to_list(1.0, [{base, -1}])), + ?assertError(badarg, float_to_list(foo, [{base, 2}])), + ?assertError(badarg, float_to_binary(1.0, [{base, 1}])), + ?assertError(badarg, float_to_binary(1.0, [{base, 37}])), + ?assertError(badarg, float_to_binary(1.0, [{base, 0}])), + ?assertError(badarg, float_to_binary(1.0, [{base, -1}])), + ?assertError(badarg, float_to_binary(foo, [{base, 2}])), + + ?assertError(badarg, binary_to_float(~"abc", 10)), + ?assertError(badarg, float_to_binary(1.0, [{base, bar}])), + + HugeInt = lists:duplicate(10_000, $1) ++ ".0", + _ = [?assertError(badarg, list_to_float(HugeInt, Base)) || + Base <- lists:seq(2, 36)], + + ok. + +test_bfs(Expect, Float, Args) -> + BinExpect = list_to_binary(Expect), + {base, Base} = lists:keyfind(base, 1, Args), + + ?assertEqual(Expect, float_to_list(Float, Args)), + ?assertEqual(Float, list_to_float(Expect, Base)), + + ?assertEqual(BinExpect, float_to_binary(Float, Args)), + ?assertEqual(Float, binary_to_float(BinExpect, Base)). + +test_rand_bfs(Base) -> + F = rand_float(), + Expect = float_to_list(F, [{base, Base}, short]), + % io:format("~p: ~p ~ts\n", [Base, F, Expect]), + case Base band (Base - 1) of + 0 -> + %% Power of two. Exact roundtrip is possible. + test_bfs(Expect, F, [{base, Base}, short]); + _ -> + %% Exact roundtrip might not be possible. + BinExpect = list_to_binary(Expect), + ok = fcmp(F, list_to_float(Expect, Base)), + ok = fcmp(F, binary_to_float(BinExpect, Base)) + end. + +fcmp(F1, F2) when F1 == 0.0, F2 == 0.0 -> ok; +fcmp(F1, F2) when (F1 - F2) / F2 < 0.0000001 -> ok. + +t_binary_to_float_2(Config) when is_list(Config) -> + %% Invalid base + ?assertError(badarg, binary_to_float(<<"1.0">>, 1)), + ?assertError(badarg, binary_to_float(<<"1.0">>, 37)), + ?assertError(badarg, binary_to_float(<<"1.0">>, 0)), + + %% Integer (no dot) + ?assertError(badarg, binary_to_float(<<"10">>, 2)), + ?assertError(badarg, binary_to_float(<<"FF">>, 16)), + + %% Not float + ?assertError(badarg, binary_to_float(<<"1.2">>, 2)), + ?assertError(badarg, binary_to_float(<<"G.0">>, 16)), + ?assertError(badarg, binary_to_float(<<"xyz">>, 16)), + ?assertError(badarg, binary_to_float(atom, 16)), + ?assertError(badarg, binary_to_float(<<"1.0">>, foo)), + ?assertError(badarg, binary_to_float(<<"">>, 16)), + + ok. + + %% Tests list_to_float/1. t_string_to_float_safe(Config) when is_list(Config) -> @@ -375,14 +549,14 @@ t_string_to_float_safe(Config) when is_list(Config) -> test_stf(127.5,"127.5"), test_stf(-199.5,"-199.5"), - {'EXIT',{badarg,_}} = (catch list_to_float(id("0"))), - {'EXIT',{badarg,_}} = (catch list_to_float(id("0..0"))), - {'EXIT',{badarg,_}} = (catch list_to_float(id("0e12"))), - {'EXIT',{badarg,_}} = (catch list_to_float(id("--0.0"))), - {'EXIT',{badarg,_}} = (catch binary_to_float(id(<<"0">>))), - {'EXIT',{badarg,_}} = (catch binary_to_float(id(<<"0..0">>))), - {'EXIT',{badarg,_}} = (catch binary_to_float(id(<<"0e12">>))), - {'EXIT',{badarg,_}} = (catch binary_to_float(id(<<"--0.0">>))), + ?assertError(badarg, list_to_float(id("0"))), + ?assertError(badarg, list_to_float(id("0..0"))), + ?assertError(badarg, list_to_float(id("0e12"))), + ?assertError(badarg, list_to_float(id("--0.0"))), + ?assertError(badarg, binary_to_float(id(<<"0">>))), + ?assertError(badarg, binary_to_float(id(<<"0..0">>))), + ?assertError(badarg, binary_to_float(id(<<"0e12">>))), + ?assertError(badarg, binary_to_float(id(<<"--0.0">>))), UBin = <<0:3,(id(<<"0.0">>))/binary,0:5>>, <<_:3,UnAlignedBin:3/binary,0:5>> = id(UBin), @@ -400,10 +574,10 @@ t_string_to_float_safe(Config) when is_list(Config) -> t_string_to_float_risky(Config) when is_list(Config) -> Many_Ones = lists:duplicate(25000, id($1)), id(list_to_float("2."++Many_Ones)), - {'EXIT', {badarg, _}} = (catch list_to_float("2"++Many_Ones)), + ?assertError(badarg, list_to_float("2"++Many_Ones)), id(binary_to_float(list_to_binary("2."++Many_Ones))), - {'EXIT', {badarg, _}} = (catch binary_to_float( + ?assertError(badarg, binary_to_float( list_to_binary("2"++Many_Ones))), ok. @@ -508,7 +682,7 @@ rand_seed() -> ok. rand_float() -> - F0 = rand:uniform() * math:pow(10, 50*rand:normal()), + F0 = rand:uniform() * math:pow(10, rand:normal(0.0, 2500.0)), case rand:uniform() of U when U < 0.5 -> -F0; _ -> F0 @@ -573,11 +747,9 @@ t_integer_to_string(Config) when is_list(Config) -> %% Invalid types lists:foreach(fun(Value) -> - {'EXIT', {badarg, _}} = - (catch erlang:integer_to_binary(Value)), - {'EXIT', {badarg, _}} = - (catch erlang:integer_to_list(Value)) - end,[atom,1.2,0.0,[$1,[$2]]]), + ?assertError(badarg, erlang:integer_to_binary(Value)), + ?assertError(badarg, erlang:integer_to_list(Value)) + end,[atom,1.2,0.0,[$1,[$2]]]), %% Base-2 integers test_its("0", 0, 2), @@ -595,11 +767,9 @@ t_integer_to_string(Config) when is_list(Config) -> 16), lists:foreach(fun(Value) -> - {'EXIT', {badarg, _}} = - (catch erlang:integer_to_binary(Value, 8)), - {'EXIT', {badarg, _}} = - (catch erlang:integer_to_list(Value, 8)) - end,[atom,1.2,0.0,[$1,[$2]]]), + ?assertError(badarg, erlang:integer_to_binary(Value, 8)), + ?assertError(badarg, erlang:integer_to_list(Value, 8)) + end,[atom,1.2,0.0,[$1,[$2]]]), ok. @@ -652,26 +822,20 @@ t_string_to_integer(Config) when is_list(Config) -> %% Invalid types lists:foreach(fun(Value) -> - {'EXIT', {badarg, _}} = - (catch bin_to_int(Value)), - {'EXIT', {badarg, _}} = - (catch list_to_integer(Value)) + ?assertError(badarg, bin_to_int(Value)), + ?assertError(badarg, list_to_integer(Value)) end,[atom,1.2,0.0,[$1,[$2]]]), %% Default base error cases lists:foreach(fun(Value) -> - {'EXIT', {badarg, _}} = - (catch bin_to_int(list_to_binary(Value))), - {'EXIT', {badarg, _}} = - (catch list_to_integer(Value)) + ?assertError(badarg, bin_to_int(list_to_binary(Value))), + ?assertError(badarg, list_to_integer(Value)) end,["1.0"," 1"," -1","","+"]), %% Custom base error cases lists:foreach(fun({Value,Base}) -> - {'EXIT', {badarg, _}} = - (catch binary_to_integer(list_to_binary(Value), Base)), - {'EXIT', {badarg, _}} = - (catch list_to_integer(Value, Base)) + ?assertError(badarg, binary_to_integer(list_to_binary(Value), Base)), + ?assertError(badarg, list_to_integer(Value, Base)) end, [{" 1",1},{" 1",37},{"2",2},{"B",11},{"b",11},{":", 16}, {"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111z",16}, @@ -688,9 +852,9 @@ t_string_to_integer(Config) when is_list(Config) -> %% System limit Digits = lists:duplicate(3_000_000, $9), - {'EXIT',{system_limit,_}} = catch list_to_integer(Digits), + ?assertError(system_limit, list_to_integer(Digits)), _ = erlang:garbage_collect(), - {'EXIT',{system_limit,_}} = catch list_to_integer(Digits, 16), + ?assertError(system_limit, list_to_integer(Digits, 16)), _ = erlang:garbage_collect(), {error,system_limit} = string:to_integer(Digits), _ = erlang:garbage_collect(), diff --git a/erts/preloaded/ebin/atomics.beam b/erts/preloaded/ebin/atomics.beam index bc83ba5b4556..847edcb3e33d 100644 Binary files a/erts/preloaded/ebin/atomics.beam and b/erts/preloaded/ebin/atomics.beam differ diff --git a/erts/preloaded/ebin/counters.beam b/erts/preloaded/ebin/counters.beam index 747824583c7e..32cb9ad0a067 100644 Binary files a/erts/preloaded/ebin/counters.beam and b/erts/preloaded/ebin/counters.beam differ diff --git a/erts/preloaded/ebin/erl_init.beam b/erts/preloaded/ebin/erl_init.beam index 333aff0a4fd2..2c0dab42d5b5 100644 Binary files a/erts/preloaded/ebin/erl_init.beam and b/erts/preloaded/ebin/erl_init.beam differ diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam index c957d18a9f06..89b41db18a98 100644 Binary files a/erts/preloaded/ebin/erl_prim_loader.beam and b/erts/preloaded/ebin/erl_prim_loader.beam differ diff --git a/erts/preloaded/ebin/erl_tracer.beam b/erts/preloaded/ebin/erl_tracer.beam index 463ccca992c3..678410b6d709 100644 Binary files a/erts/preloaded/ebin/erl_tracer.beam and b/erts/preloaded/ebin/erl_tracer.beam differ diff --git a/erts/preloaded/ebin/erts_code_purger.beam b/erts/preloaded/ebin/erts_code_purger.beam index 363e1a0e78f0..2f6990332cc7 100644 Binary files a/erts/preloaded/ebin/erts_code_purger.beam and b/erts/preloaded/ebin/erts_code_purger.beam differ diff --git a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam index 77935cf98df6..6dc7c0928404 100644 Binary files a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam and b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam differ diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam index ee0c7acce5b3..cf6b27a87715 100644 Binary files a/erts/preloaded/ebin/erts_internal.beam and b/erts/preloaded/ebin/erts_internal.beam differ diff --git a/erts/preloaded/ebin/erts_literal_area_collector.beam b/erts/preloaded/ebin/erts_literal_area_collector.beam index 3f056e35b03d..698e5dae3271 100644 Binary files a/erts/preloaded/ebin/erts_literal_area_collector.beam and b/erts/preloaded/ebin/erts_literal_area_collector.beam differ diff --git a/erts/preloaded/ebin/erts_trace_cleaner.beam b/erts/preloaded/ebin/erts_trace_cleaner.beam index fc9f5d419ce9..c8c7745b4508 100644 Binary files a/erts/preloaded/ebin/erts_trace_cleaner.beam and b/erts/preloaded/ebin/erts_trace_cleaner.beam differ diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam index 144ad26d9028..ff2ce344b1e5 100644 Binary files a/erts/preloaded/ebin/init.beam and b/erts/preloaded/ebin/init.beam differ diff --git a/erts/preloaded/ebin/persistent_term.beam b/erts/preloaded/ebin/persistent_term.beam index 669dedcf0422..a479922e584e 100644 Binary files a/erts/preloaded/ebin/persistent_term.beam and b/erts/preloaded/ebin/persistent_term.beam differ diff --git a/erts/preloaded/ebin/prim_buffer.beam b/erts/preloaded/ebin/prim_buffer.beam index 7dd678233e97..3793b89264e6 100644 Binary files a/erts/preloaded/ebin/prim_buffer.beam and b/erts/preloaded/ebin/prim_buffer.beam differ diff --git a/erts/preloaded/ebin/prim_eval.beam b/erts/preloaded/ebin/prim_eval.beam index 08d5c6c5b317..a021b77632ea 100644 Binary files a/erts/preloaded/ebin/prim_eval.beam and b/erts/preloaded/ebin/prim_eval.beam differ diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam index 09f7b96eac93..16bdb0a4f893 100644 Binary files a/erts/preloaded/ebin/prim_file.beam and b/erts/preloaded/ebin/prim_file.beam differ diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam index e4959a8dc8ba..05e4d6eb2682 100644 Binary files a/erts/preloaded/ebin/prim_inet.beam and b/erts/preloaded/ebin/prim_inet.beam differ diff --git a/erts/preloaded/ebin/prim_net.beam b/erts/preloaded/ebin/prim_net.beam index 3c4c4d71f879..ef920c328cd1 100644 Binary files a/erts/preloaded/ebin/prim_net.beam and b/erts/preloaded/ebin/prim_net.beam differ diff --git a/erts/preloaded/ebin/prim_socket.beam b/erts/preloaded/ebin/prim_socket.beam index 0868d3648581..771400aca785 100644 Binary files a/erts/preloaded/ebin/prim_socket.beam and b/erts/preloaded/ebin/prim_socket.beam differ diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam index 264b269c65cd..357a1f8ea3fd 100644 Binary files a/erts/preloaded/ebin/prim_zip.beam and b/erts/preloaded/ebin/prim_zip.beam differ diff --git a/erts/preloaded/ebin/socket_registry.beam b/erts/preloaded/ebin/socket_registry.beam index fd78d5050ba6..59474801adce 100644 Binary files a/erts/preloaded/ebin/socket_registry.beam and b/erts/preloaded/ebin/socket_registry.beam differ diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam index f006c9dbe178..7c9a3f41f911 100644 Binary files a/erts/preloaded/ebin/zlib.beam and b/erts/preloaded/ebin/zlib.beam differ diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 53424e1f9afb..ac211c390bee 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -429,7 +429,7 @@ A list of binaries. This datatype is useful to use together with -export([atom_to_list/1, binary_part/2, binary_part/3]). -export([binary_to_atom/1, binary_to_atom/2]). -export([binary_to_existing_atom/1, binary_to_existing_atom/2]). --export([binary_to_float/1]). +-export([binary_to_float/1, binary_to_float/2]). -export([binary_to_integer/1,binary_to_integer/2]). -export([binary_to_list/1]). -export([binary_to_list/3, binary_to_term/1, binary_to_term/2]). @@ -459,7 +459,8 @@ A list of binaries. This datatype is useful to use together with -export([iolist_size/1, iolist_to_binary/1, iolist_to_iovec/1]). -export([is_alive/0, is_builtin/3, is_map_key/2, is_process_alive/1, length/1]). -export([link/1, link/2, list_to_atom/1, list_to_binary/1]). --export([list_to_bitstring/1, list_to_existing_atom/1, list_to_float/1]). +-export([list_to_bitstring/1, list_to_existing_atom/1]). +-export([list_to_float/1, list_to_float/2]). -export([list_to_integer/1, list_to_integer/2]). -export([list_to_pid/1, list_to_port/1, list_to_ref/1, list_to_tuple/1, loaded/0]). -export([localtime/0, make_ref/0]). @@ -1029,6 +1030,30 @@ Failure: `badarg` if `Binary` contains an invalid representation of a float. binary_to_float(_Binary) -> erlang:nif_error(undefined). +-doc """ +Returns the float whose text representation in base `Base` is `Binary`. + +## Example + +```erlang +1> binary_to_float(<<"3FF.0">>, 16). +1023 +2> binary_to_float(<<"101.0">>, 2). +5.0 +``` + +[`binary_to_float/2`](`binary_to_float/2`) accepts the same string formats +as `list_to_float/2`. + +Failure: `badarg` if `Binary` contains a invalid representation of a float. +""". +-doc #{ category => terms }. +-spec binary_to_float(Binary, Base) -> float() when + Binary :: binary(), + Base :: 2..36. +binary_to_float(_Binary, _Base) -> + erlang:nif_error(undefined). + -doc """ Returns an integer whose text representation is `Binary`. @@ -2797,7 +2822,8 @@ decimal point formatting. -spec float_to_binary(Float, Options) -> binary() when Float :: float(), Options :: [Option], - Option :: {decimals, Decimals :: 0..253} | + Option :: {base, Base :: 2..36} | + {decimals, Decimals :: 0..253} | {scientific, Decimals :: 0..249} | compact | short. @@ -2831,6 +2857,8 @@ Available options: is used (scientific notation or normal decimal notation). Floats outside the range (-2⁵³, 2⁵³) are always formatted using scientific notation to avoid confusing results when doing arithmetic operations. +- If option `base` is specified, the float is converted in the specified base + (between 2 and 36). The default base is 10. - If `Options` is `[]`, the function behaves as `float_to_list/1`. ## Examples @@ -2859,7 +2887,8 @@ In the last example, [`float_to_list(0.1+0.2)`](`float_to_list/1`) evaluates to -spec float_to_list(Float, Options) -> string() when Float :: float(), Options :: [Option], - Option :: {decimals, Decimals :: 0..253} | + Option :: {base, Base :: 2..36} | + {decimals, Decimals :: 0..253} | {scientific, Decimals :: 0..249} | compact | short. @@ -4088,6 +4117,42 @@ Failure: `badarg` if `String` contains a invalid representation of a float. list_to_float(_String) -> erlang:nif_error(undefined). +-doc """ +Returns the float whose text representation in base `Base` is `String`. + +The float string format is the same as the format for +[Erlang float literals](`e:system:data_types.md`) except for that underscores +are not permitted. + +Failure: `badarg` if `String` contains an invalid representation of a float. + +## Examples + +```erlang +1> list_to_float("1.01", 2). +1.25 +2> list_to_float("+1.01", 2). +1.25 +3> list_to_float("-1.01", 2). +-1.25 +4> list_to_float("-3FF.0", 16). +-1023.0 +5> list_to_float("Base36IsFun.0", 36). +41313437507787071.0 +6> list_to_float("102", 2). +** exception error: bad argument + in function list_to_float/2 + called as list_to_float("102",2) + *** argument 1: not a textual representation of a float +``` +""". +-doc #{ category => terms }. +-spec list_to_float(String, Base) -> float() when + String :: string(), + Base :: 2..36. +list_to_float(_String, _Base) -> + erlang:nif_error(undefined). + -doc """ Returns an integer whose text representation is `String`. diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index d8e2efa4a91b..4c57fb5b510a 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -274,6 +274,7 @@ bif(binary_to_existing_atom, 2) -> true; bif(binary_to_integer, 1) -> true; bif(binary_to_integer, 2) -> true; bif(binary_to_float, 1) -> true; +bif(binary_to_float, 2) -> true; bif(binary_to_list, 1) -> true; bif(binary_to_list, 3) -> true; bif(binary_to_term, 1) -> true; @@ -357,6 +358,7 @@ bif(list_to_binary, 1) -> true; bif(list_to_bitstring, 1) -> true; bif(list_to_existing_atom, 1) -> true; bif(list_to_float, 1) -> true; +bif(list_to_float, 2) -> true; bif(list_to_integer, 1) -> true; bif(list_to_integer, 2) -> true; bif(list_to_pid, 1) -> true;