authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2022-04-03 08:47:17-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-04-04 16:04:35-04:00
logb4bf3bdf7eac05c5e4ff887294385946f4dd5f3f
tree714937345eadfd8ccb5332ae87179083260e4029
parent364e53f3bf6b5aa4e5e7eba5d790c5b957007067

std.fmt: Fix incorrect behavior with large floating point integers.

I consider this an interim workaround/hack until #1299 is finished. There is a bug in the original C implementation of the errol3 (and errol4) algorithm that can result in undefined behavior or an obviously incorrect result (leading ':' in the output) This change checks for those two problems and uses a slower fallback path if they occur. I can't guarantee that this will always produce the correct result, but since the workaround is only used if the original algorithm is guaranteed to fail, it should never turn a previously-correct result into an incorrect one. Fixes #11283

2 files changed, 8 insertions(+), 1 deletions(-)

lib/std/fmt.zig+2
...@@ -2299,6 +2299,8 @@ test "float.decimal" {...@@ -2299,6 +2299,8 @@ test "float.decimal" {
2299 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)});2299 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)});
2300 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)});2300 try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)});
2301 try expectFmt("f64: 10000000000000.00", "f64: {d:.2}", .{@as(f64, 9999999999999.999)});2301 try expectFmt("f64: 10000000000000.00", "f64: {d:.2}", .{@as(f64, 9999999999999.999)});
2302 try expectFmt("f64: 10000000000000000000000000000000000000", "f64: {d}", .{@as(f64, 1e37)});
2303 try expectFmt("f64: 100000000000000000000000000000000000000", "f64: {d}", .{@as(f64, 1e38)});
2302}2304}
23032305
2304test "float.libc.sanity" {2306test "float.libc.sanity" {
lib/std/fmt/errol.zig+6-1
...@@ -106,7 +106,10 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {...@@ -106,7 +106,10 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
106 } else if (val >= 16.0 and val < 9.007199254740992e15) {106 } else if (val >= 16.0 and val < 9.007199254740992e15) {
107 return errolFixed(val, buffer);107 return errolFixed(val, buffer);
108 }108 }
109 return errolSlow(val, buffer);
110}
109111
112fn errolSlow(val: f64, buffer: []u8) FloatDecimal {
110 // normalize the midpoint113 // normalize the midpoint
111114
112 const e = math.frexp(val).exponent;115 const e = math.frexp(val).exponent;
...@@ -336,7 +339,9 @@ fn errolInt(val: f64, buffer: []u8) FloatDecimal {...@@ -336,7 +339,9 @@ fn errolInt(val: f64, buffer: []u8) FloatDecimal {
336 var buf_index = u64toa(m64, buffer) - 1;339 var buf_index = u64toa(m64, buffer) - 1;
337340
338 if (mi != 0) {341 if (mi != 0) {
339 buffer[buf_index - 1] += @boolToInt(buffer[buf_index] >= '5');342 const round_up = buffer[buf_index] >= '5';
343 if (buf_index == 0 or (round_up and buffer[buf_index - 1] == '9')) return errolSlow(val, buffer);
344 buffer[buf_index - 1] += @boolToInt(round_up);
340 } else {345 } else {
341 buf_index += 1;346 buf_index += 1;
342 }347 }