From a8e0c9efff08d54372f83b23018dff3704543cba Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 20 Aug 2025 22:41:25 +0300 Subject: [PATCH] strconv: produce a maximum of 8 digits after the `.` for f32.str() (fix #25141) (#25144) --- vlib/strconv/f32_f64_to_string_test.v | 32 +++++++++++++++++ .../f32_str_should_be_different_test.v | 35 +++++++++++++++++++ vlib/strconv/ftoa.c.v | 8 ++--- vlib/strconv/utilities.c.v | 12 +++---- vlib/v/slow_tests/repl/fn_calls.repl | 2 +- 5 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 vlib/strconv/f32_str_should_be_different_test.v diff --git a/vlib/strconv/f32_f64_to_string_test.v b/vlib/strconv/f32_f64_to_string_test.v index d73de5702e..06003fa4c8 100644 --- a/vlib/strconv/f32_f64_to_string_test.v +++ b/vlib/strconv/f32_f64_to_string_test.v @@ -172,3 +172,35 @@ fn test_float_to_str() { // assert ftoa.f64_to_str(0.3456789123456, 4)=="3.4568e-01" // assert ftoa.f32_to_str(0.345678, 3)=="3.457e-01" } + +fn test_issue_25141_1() { + assert strconv.ftoa_long_32(0.1234567901) == '0.12345679' +} + +fn test_issue_25141_2() { + assert strconv.f32_to_str_l(math.f32_from_bits(1055100472)) == '0.44444442' +} + +fn test_issue_25141_3() { + assert strconv.f32_to_str_l(math.f32_from_bits(1055100473)) == '0.44444445' +} + +fn test_issue_25141_4() { + assert strconv.f32_to_str_l(math.f32_from_bits(1055100474)) == '0.44444448' +} + +fn test_issue_25141_5() { + assert strconv.f32_to_str_l(math.f32_from_bits(1055100475)) == '0.4444445' +} + +fn test_issue_25141_0001() { + assert strconv.f32_to_str_l(math.f32_from_bits(953267991)) == '0.0001' + assert strconv.f32_to_str_l(math.f32_from_bits(953267992)) == '0.000100000005' + assert strconv.f32_to_str_l(math.f32_from_bits(953267993)) == '0.00010000001' + assert strconv.f32_to_str_l(math.f32_from_bits(953267994)) == '0.00010000002' + assert strconv.f32_to_str_l(math.f32_from_bits(953267995)) == '0.00010000003' + assert strconv.f32_to_str_l(math.f32_from_bits(953267996)) == '0.000100000034' + assert strconv.f32_to_str_l(math.f32_from_bits(953267997)) == '0.00010000004' + assert strconv.f32_to_str_l(math.f32_from_bits(953267998)) == '0.00010000005' + assert strconv.f32_to_str_l(math.f32_from_bits(953267999)) == '0.000100000056' +} diff --git a/vlib/strconv/f32_str_should_be_different_test.v b/vlib/strconv/f32_str_should_be_different_test.v new file mode 100644 index 0000000000..6e25a496c7 --- /dev/null +++ b/vlib/strconv/f32_str_should_be_different_test.v @@ -0,0 +1,35 @@ +import math + +fn test_around() { + // do not pass 0 + for f in [f32(1e-12), 1e-9, 1e-6, 1e-4, 0.1, 0.2, 0.5, 1, 2, 5, 10, 1e6, 1e7, 1e9, 1e12] { + check_around(f, 1000) + } +} + +fn check_around(fval f32, total int) { + check(fval, total) + check(-fval, total) +} + +fn check(fval f32, total int) { + middle_bits := math.f32_bits(fval) + start_bits := middle_bits - u32(total / 2) + end_bits := middle_bits + u32(total / 2) + min, max := if start_bits < end_bits { start_bits, end_bits } else { end_bits, start_bits } + println('> check_around ${total} f32 values around fval: ${fval:26.12f}, middle_bits for fval: ${middle_bits:12} | min: ${min:12} | max: ${max:12}') + for ux in min .. max { + x := math.f32_from_bits(ux) + if math.is_nan(x) { + continue + } + uy := ux - 1 + y := math.f32_from_bits(uy) + if x == y { + continue + } + sx := x.str() + sy := y.str() + assert sx != sy, 'math.f32_from_bits(${ux})' + } +} diff --git a/vlib/strconv/ftoa.c.v b/vlib/strconv/ftoa.c.v index ef7f9490f8..ebf75725a4 100644 --- a/vlib/strconv/ftoa.c.v +++ b/vlib/strconv/ftoa.c.v @@ -19,7 +19,6 @@ https://github.com/cespare/ryu/tree/ba56a33f39e3bbbfa409095d0f9ae168a595feea */ // ftoa_64 returns a string in scientific notation with max 17 digits after the dot. -// // Example: assert strconv.ftoa_64(123.1234567891011121) == '1.2312345678910111e+02' @[inline] pub fn ftoa_64(f f64) string { @@ -27,7 +26,6 @@ pub fn ftoa_64(f f64) string { } // ftoa_long_64 returns `f` as a `string` in decimal notation with a maximum of 17 digits after the dot. -// // Example: assert strconv.f64_to_str_l(123.1234567891011121) == '123.12345678910111' @[inline] pub fn ftoa_long_64(f f64) string { @@ -35,16 +33,14 @@ pub fn ftoa_long_64(f f64) string { } // ftoa_32 returns a `string` in scientific notation with max 8 digits after the dot. -// // Example: assert strconv.ftoa_32(34.1234567) == '3.4123455e+01' @[inline] pub fn ftoa_32(f f32) string { return f32_to_str(f, 8) } -// ftoa_long_32 returns `f` as a `string` in decimal notation with a maximum of 6 digits after the dot. -// -// Example: assert strconv.ftoa_long_32(34.1234567) == '34.12346' +// ftoa_long_32 returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot. +// Example: assert strconv.ftoa_long_32(0.1234567901) == '0.12345679' @[inline] pub fn ftoa_long_32(f f32) string { return f32_to_str_l(f) diff --git a/vlib/strconv/utilities.c.v b/vlib/strconv/utilities.c.v index 2b4a5a4748..23b82d7dd9 100644 --- a/vlib/strconv/utilities.c.v +++ b/vlib/strconv/utilities.c.v @@ -23,24 +23,24 @@ f64 to string with string format */ // TODO: Investigate precision issues -// f32_to_str_l returns `f` as a `string` in decimal notation with a maximum of 6 digits after the dot. -// -// Example: assert strconv.f32_to_str_l(34.1234567) == '34.12346' +// f32_to_str_l returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot. +// Example: assert strconv.f32_to_str_l(0.1234567891) == '0.12345679' +// Example: assert strconv.f32_to_str_l(34.1234567891) == '34.123455' @[manualfree] pub fn f32_to_str_l(f f32) string { - s := f32_to_str(f, 6) + s := f32_to_str(f, 8) res := fxx_to_str_l_parse(s) unsafe { s.free() } return res } -// f32_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 6 digits after the dot. +// f32_to_str_l_with_dot returns `f` as a `string` in decimal notation with a maximum of 8 digits after the dot. // If the decimal digits after the dot are zero, a '.0' is appended for clarity. // // Example: assert strconv.f32_to_str_l_with_dot(34.) == '34.0' @[manualfree] pub fn f32_to_str_l_with_dot(f f32) string { - s := f32_to_str(f, 6) + s := f32_to_str(f, 8) res := fxx_to_str_l_parse_with_dot(s) unsafe { s.free() } return res diff --git a/vlib/v/slow_tests/repl/fn_calls.repl b/vlib/v/slow_tests/repl/fn_calls.repl index 8d18dfbcc1..18bac2cf05 100644 --- a/vlib/v/slow_tests/repl/fn_calls.repl +++ b/vlib/v/slow_tests/repl/fn_calls.repl @@ -4,7 +4,7 @@ fn test() { println('foo') } fn test2(a int) { println(a) } test() test2(123) ===output=== --0.2623749 +-0.26237485 0.7376251 foo 123