authorgravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-04-17 22:58:10+12:00
committergravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-04-23 17:22:51+12:00
logd8ba1bc12054712dec731db0c4062a5df0d627c6
treef41af692f39b03c02c9fab3c071a36715a20fa82
parent8503eff8c12697c35ab5c73d6651c4b996339706

Improve fmt float-printing

- Fix errors printing very small numbers - Add explicit scientific output mode - Add rounding based on a specific precision for both decimal/exp modes. - Test and confirm exp/decimal against libc for all f32 values. Various changes to better match libc.

2 files changed, 456 insertions(+), 59 deletions(-)

std/fmt/errol/index.zig+73-3
...@@ -12,13 +12,79 @@ pub const FloatDecimal = struct {...@@ -12,13 +12,79 @@ pub const FloatDecimal = struct {
12 exp: i32,12 exp: i32,
13};13};
1414
15pub const RoundMode = enum {
16 // Round only the fractional portion (e.g. 1234.23 has precision 2)
17 Decimal,
18 // Round the entire whole/fractional portion (e.g. 1.23423e3 has precision 5)
19 Scientific,
20};
21
22/// Round a FloatDecimal as returned by errol3 to the specified fractional precision.
23/// All digits after the specified precision should be considered invalid.
24pub fn roundToPrecision(float_decimal: &FloatDecimal, precision: usize, mode: RoundMode) void {
25 // The round digit refers to the index which we should look at to determine
26 // whether we need to round to match the specified precision.
27 var round_digit: usize = 0;
28
29 switch (mode) {
30 RoundMode.Decimal => {
31 if (float_decimal.exp >= 0) {
32 round_digit = precision + usize(float_decimal.exp);
33 } else {
34 // if a small negative exp, then adjust we need to offset by the number
35 // of leading zeros that will occur.
36 const min_exp_required = usize(-float_decimal.exp);
37 if (precision > min_exp_required) {
38 round_digit = precision - min_exp_required;
39 }
40 }
41 },
42 RoundMode.Scientific => {
43 round_digit = 1 + precision;
44 },
45 }
46
47 // It suffices to look at just this digit. We don't round and propagate say 0.04999 to 0.05
48 // first, and then to 0.1 in the case of a {.1} single precision.
49
50 // Find the digit which will signify the round point and start rounding backwards.
51 if (round_digit < float_decimal.digits.len and float_decimal.digits[round_digit] - '0' >= 5) {
52 assert(round_digit >= 0);
53
54 var i = round_digit;
55 while (true) {
56 if (i == 0) {
57 // Rounded all the way past the start. This was of the form 9.999...
58 // Slot the new digit in place and increase the exponent.
59 float_decimal.exp += 1;
60
61 // Re-size the buffer to use the reserved leading byte.
62 const one_before = @intToPtr(&u8, @ptrToInt(&float_decimal.digits[0]) - 1);
63 float_decimal.digits = one_before[0..float_decimal.digits.len + 1];
64 float_decimal.digits[0] = '1';
65 return;
66 }
67
68 i -= 1;
69
70 const new_value = (float_decimal.digits[i] - '0' + 1) % 10;
71 float_decimal.digits[i] = new_value + '0';
72
73 // must continue rounding until non-9
74 if (new_value != 0) {
75 return;
76 }
77 }
78 }
79}
80
15/// Corrected Errol3 double to ASCII conversion.81/// Corrected Errol3 double to ASCII conversion.
16pub fn errol3(value: f64, buffer: []u8) FloatDecimal {82pub fn errol3(value: f64, buffer: []u8) FloatDecimal {
17 const bits = @bitCast(u64, value);83 const bits = @bitCast(u64, value);
18 const i = tableLowerBound(bits);84 const i = tableLowerBound(bits);
19 if (i < enum3.len and enum3[i] == bits) {85 if (i < enum3.len and enum3[i] == bits) {
20 const data = enum3_data[i];86 const data = enum3_data[i];
21 const digits = buffer[0..data.str.len];87 const digits = buffer[1..data.str.len + 1];
22 mem.copy(u8, digits, data.str);88 mem.copy(u8, digits, data.str);
23 return FloatDecimal {89 return FloatDecimal {
24 .digits = digits,90 .digits = digits,
...@@ -98,7 +164,11 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {...@@ -98,7 +164,11 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
98 }164 }
99165
100 // digit generation166 // digit generation
101 var buf_index: usize = 0;167
168 // We generate digits starting at index 1. If rounding a buffer later then it may be
169 // required to generate a preceeding digit in some cases (9.999) in which case we use
170 // the 0-index for this extra digit.
171 var buf_index: usize = 1;
102 while (true) {172 while (true) {
103 var hdig = u8(math.floor(high.val));173 var hdig = u8(math.floor(high.val));
104 if ((high.val == f64(hdig)) and (high.off < 0))174 if ((high.val == f64(hdig)) and (high.off < 0))
...@@ -128,7 +198,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {...@@ -128,7 +198,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
128 buf_index += 1;198 buf_index += 1;
129199
130 return FloatDecimal {200 return FloatDecimal {
131 .digits = buffer[0..buf_index],201 .digits = buffer[1..buf_index],
132 .exp = exp,202 .exp = exp,
133 };203 };
134}204}
std/fmt/index.zig+383-56
...@@ -4,7 +4,7 @@ const debug = std.debug;...@@ -4,7 +4,7 @@ const debug = std.debug;
4const assert = debug.assert;4const assert = debug.assert;
5const mem = std.mem;5const mem = std.mem;
6const builtin = @import("builtin");6const builtin = @import("builtin");
7const errol3 = @import("errol/index.zig").errol3;7const errol = @import("errol/index.zig");
88
9const max_int_digits = 65;9const max_int_digits = 65;
1010
...@@ -22,6 +22,8 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -22,6 +22,8 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
22 IntegerWidth,22 IntegerWidth,
23 Float,23 Float,
24 FloatWidth,24 FloatWidth,
25 FloatScientific,
26 FloatScientificWidth,
25 Character,27 Character,
26 Buf,28 Buf,
27 BufWidth,29 BufWidth,
...@@ -87,6 +89,9 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -87,6 +89,9 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
87 's' => {89 's' => {
88 state = State.Buf;90 state = State.Buf;
89 },91 },
92 'e' => {
93 state = State.FloatScientific;
94 },
90 '.' => {95 '.' => {
91 state = State.Float;96 state = State.Float;
92 },97 },
...@@ -133,9 +138,33 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),...@@ -133,9 +138,33 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
133 '0' ... '9' => {},138 '0' ... '9' => {},
134 else => @compileError("Unexpected character in format string: " ++ []u8{c}),139 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
135 },140 },
141 State.FloatScientific => switch (c) {
142 '}' => {
143 try formatFloatScientific(args[next_arg], null, context, Errors, output);
144 next_arg += 1;
145 state = State.Start;
146 start_index = i + 1;
147 },
148 '0' ... '9' => {
149 width_start = i;
150 state = State.FloatScientificWidth;
151 },
152 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
153 },
154 State.FloatScientificWidth => switch (c) {
155 '}' => {
156 width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
157 try formatFloatScientific(args[next_arg], width, context, Errors, output);
158 next_arg += 1;
159 state = State.Start;
160 start_index = i + 1;
161 },
162 '0' ... '9' => {},
163 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
164 },
136 State.Float => switch (c) {165 State.Float => switch (c) {
137 '}' => {166 '}' => {
138 try formatFloatDecimal(args[next_arg], 0, context, Errors, output);167 try formatFloatDecimal(args[next_arg], null, context, Errors, output);
139 next_arg += 1;168 next_arg += 1;
140 state = State.Start;169 state = State.Start;
141 start_index = i + 1;170 start_index = i + 1;
...@@ -199,7 +228,7 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@...@@ -199,7 +228,7 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
199 return formatInt(value, 10, false, 0, context, Errors, output);228 return formatInt(value, 10, false, 0, context, Errors, output);
200 },229 },
201 builtin.TypeId.Float => {230 builtin.TypeId.Float => {
202 return formatFloat(value, context, Errors, output);231 return formatFloatScientific(value, null, context, Errors, output);
203 },232 },
204 builtin.TypeId.Void => {233 builtin.TypeId.Void => {
205 return output(context, "void");234 return output(context, "void");
...@@ -257,81 +286,237 @@ pub fn formatBuf(buf: []const u8, width: usize,...@@ -257,81 +286,237 @@ pub fn formatBuf(buf: []const u8, width: usize,
257 }286 }
258}287}
259288
260pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {289// Print a float in scientific notation to the specified precision. Null uses full precision.
290// It should be the case that every full precision, printed value can be re-parsed back to the
291// same type unambiguously.
292pub fn formatFloatScientific(value: var, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
261 var x = f64(value);293 var x = f64(value);
262294
263 // Errol doesn't handle these special cases.295 // Errol doesn't handle these special cases.
264 if (math.isNan(x)) {
265 return output(context, "NaN");
266 }
267 if (math.signbit(x)) {296 if (math.signbit(x)) {
268 try output(context, "-");297 try output(context, "-");
269 x = -x;298 x = -x;
270 }299 }
300
301 if (math.isNan(x)) {
302 return output(context, "nan");
303 }
271 if (math.isPositiveInf(x)) {304 if (math.isPositiveInf(x)) {
272 return output(context, "Infinity");305 return output(context, "inf");
273 }306 }
274 if (x == 0.0) {307 if (x == 0.0) {
275 return output(context, "0.0");308 try output(context, "0");
309
310 if (maybe_precision) |precision| {
311 if (precision != 0) {
312 try output(context, ".");
313 var i: usize = 0;
314 while (i < precision) : (i += 1) {
315 try output(context, "0");
316 }
317 }
318 } else {
319 try output(context, ".0");
320 }
321
322 try output(context, "e+00");
323 return;
276 }324 }
277325
278 var buffer: [32]u8 = undefined;326 var buffer: [32]u8 = undefined;
279 const float_decimal = errol3(x, buffer[0..]);327 var float_decimal = errol.errol3(x, buffer[0..]);
280 try output(context, float_decimal.digits[0..1]);328
281 try output(context, ".");329 if (maybe_precision) |precision| {
282 if (float_decimal.digits.len > 1) {330 errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific);
283 const num_digits = if (@typeOf(value) == f32)331
284 math.min(usize(9), float_decimal.digits.len)332 try output(context, float_decimal.digits[0..1]);
285 else333
286 float_decimal.digits.len;334 // {e0} case prints no `.`
287 try output(context, float_decimal.digits[1 .. num_digits]);335 if (precision != 0) {
336 try output(context, ".");
337
338 var printed: usize = 0;
339 if (float_decimal.digits.len > 1) {
340 const num_digits = math.min(float_decimal.digits.len, precision + 1);
341 try output(context, float_decimal.digits[1 .. num_digits]);
342 printed += num_digits - 1;
343 }
344
345 while (printed < precision) : (printed += 1) {
346 try output(context, "0");
347 }
348 }
288 } else {349 } else {
289 try output(context, "0");350 try output(context, float_decimal.digits[0..1]);
351 try output(context, ".");
352 if (float_decimal.digits.len > 1) {
353 const num_digits = if (@typeOf(value) == f32)
354 math.min(usize(9), float_decimal.digits.len)
355 else
356 float_decimal.digits.len;
357
358 try output(context, float_decimal.digits[1 .. num_digits]);
359 } else {
360 try output(context, "0");
361 }
290 }362 }
291363
292 if (float_decimal.exp != 1) {364 try output(context, "e");
293 try output(context, "e");365 const exp = float_decimal.exp - 1;
294 try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output);366
367 if (exp >= 0) {
368 try output(context, "+");
369 if (exp > -10 and exp < 10) {
370 try output(context, "0");
371 }
372 try formatInt(exp, 10, false, 0, context, Errors, output);
373 } else {
374 try output(context, "-");
375 if (exp > -10 and exp < 10) {
376 try output(context, "0");
377 }
378 try formatInt(-exp, 10, false, 0, context, Errors, output);
295 }379 }
296}380}
297381
298pub fn formatFloatDecimal(value: var, precision: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {382// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
383// By default floats are printed at full precision (no rounding).
384pub fn formatFloatDecimal(value: var, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
299 var x = f64(value);385 var x = f64(value);
300386
301 // Errol doesn't handle these special cases.387 // Errol doesn't handle these special cases.
302 if (math.isNan(x)) {
303 return output(context, "NaN");
304 }
305 if (math.signbit(x)) {388 if (math.signbit(x)) {
306 try output(context, "-");389 try output(context, "-");
307 x = -x;390 x = -x;
308 }391 }
392
393 if (math.isNan(x)) {
394 return output(context, "nan");
395 }
309 if (math.isPositiveInf(x)) {396 if (math.isPositiveInf(x)) {
310 return output(context, "Infinity");397 return output(context, "inf");
311 }398 }
312 if (x == 0.0) {399 if (x == 0.0) {
313 return output(context, "0.0");400 try output(context, "0");
401
402 if (maybe_precision) |precision| {
403 if (precision != 0) {
404 try output(context, ".");
405 var i: usize = 0;
406 while (i < precision) : (i += 1) {
407 try output(context, "0");
408 }
409 } else {
410 try output(context, ".0");
411 }
412 } else {
413 try output(context, "0");
414 }
415
416 return;
314 }417 }
315418
419 // non-special case, use errol3
316 var buffer: [32]u8 = undefined;420 var buffer: [32]u8 = undefined;
317 const float_decimal = errol3(x, buffer[0..]);421 var float_decimal = errol.errol3(x, buffer[0..]);
318422
319 const num_left_digits = if (float_decimal.exp > 0) usize(float_decimal.exp) else 1;423 if (maybe_precision) |precision| {
320424 errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
321 try output(context, float_decimal.digits[0 .. num_left_digits]);425
322 try output(context, ".");426 // exp < 0 means the leading is always 0 as errol result is normalized.
323 if (float_decimal.digits.len > 1) {427 var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
324 const num_valid_digtis = if (@typeOf(value) == f32) math.min(usize(7), float_decimal.digits.len)428
325 else429 // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
326 float_decimal.digits.len;430 var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
327431
328 const num_right_digits = if (precision != 0)432 if (num_digits_whole > 0) {
329 math.min(precision, (num_valid_digtis-num_left_digits))433 // We may have to zero pad, for instance 1e4 requires zero padding.
330 else434 try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
331 num_valid_digtis - num_left_digits;435
332 try output(context, float_decimal.digits[num_left_digits .. (num_left_digits + num_right_digits)]);436 var i = num_digits_whole_no_pad;
437 while (i < num_digits_whole) : (i += 1) {
438 try output(context, "0");
439 }
440 } else {
441 try output(context , "0");
442 }
443
444 // {.0} special case doesn't want a trailing '.'
445 if (precision == 0) {
446 return;
447 }
448
449 try output(context, ".");
450
451 // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's.
452 var printed: usize = 0;
453
454 // Zero-fill until we reach significant digits or run out of precision.
455 if (float_decimal.exp <= 0) {
456 const zero_digit_count = usize(-float_decimal.exp);
457 const zeros_to_print = math.min(zero_digit_count, precision);
458
459 var i: usize = 0;
460 while (i < zeros_to_print) : (i += 1) {
461 try output(context, "0");
462 printed += 1;
463 }
464
465 if (printed >= precision) {
466 return;
467 }
468 }
469
470 // Remaining fractional portion, zero-padding if insufficient.
471 debug.assert(precision >= printed);
472 if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) {
473 try output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]);
474 return;
475 } else {
476 try output(context, float_decimal.digits[num_digits_whole_no_pad ..]);
477 printed += float_decimal.digits.len - num_digits_whole_no_pad;
478
479 while (printed < precision) : (printed += 1) {
480 try output(context, "0");
481 }
482 }
333 } else {483 } else {
334 try output(context, "0");484 // exp < 0 means the leading is always 0 as errol result is normalized.
485 var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
486
487 // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
488 var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
489
490 if (num_digits_whole > 0) {
491 // We may have to zero pad, for instance 1e4 requires zero padding.
492 try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
493
494 var i = num_digits_whole_no_pad;
495 while (i < num_digits_whole) : (i += 1) {
496 try output(context, "0");
497 }
498 } else {
499 try output(context , "0");
500 }
501
502 // Omit `.` if no fractional portion
503 if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) {
504 return;
505 }
506
507 try output(context, ".");
508
509 // Zero-fill until we reach significant digits or run out of precision.
510 if (float_decimal.exp < 0) {
511 const zero_digit_count = usize(-float_decimal.exp);
512
513 var i: usize = 0;
514 while (i < zero_digit_count) : (i += 1) {
515 try output(context, "0");
516 }
517 }
518
519 try output(context, float_decimal.digits[num_digits_whole_no_pad ..]);
335 }520 }
336}521}
337522
...@@ -598,32 +783,81 @@ test "fmt.format" {...@@ -598,32 +783,81 @@ test "fmt.format" {
598 // TODO get these tests passing in release modes783 // TODO get these tests passing in release modes
599 // https://github.com/zig-lang/zig/issues/564784 // https://github.com/zig-lang/zig/issues/564
600 if (builtin.mode == builtin.Mode.Debug) {785 if (builtin.mode == builtin.Mode.Debug) {
786 {
787 var buf1: [32]u8 = undefined;
788 const value: f32 = 1.34;
789 const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
790 assert(mem.eql(u8, result, "f32: 1.34000003e+00\n"));
791 }
601 {792 {
602 var buf1: [32]u8 = undefined;793 var buf1: [32]u8 = undefined;
603 const value: f32 = 12.34;794 const value: f32 = 12.34;
604 const result = try bufPrint(buf1[0..], "f32: {}\n", value);795 const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
605 assert(mem.eql(u8, result, "f32: 1.23400001e1\n"));796 assert(mem.eql(u8, result, "f32: 1.23400001e+01\n"));
606 }797 }
607 {798 {
608 var buf1: [32]u8 = undefined;799 var buf1: [32]u8 = undefined;
609 const value: f64 = -12.34e10;800 const value: f64 = -12.34e10;
610 const result = try bufPrint(buf1[0..], "f64: {}\n", value);801 const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
611 assert(mem.eql(u8, result, "f64: -1.234e11\n"));802 assert(mem.eql(u8, result, "f64: -1.234e+11\n"));
803 }
804 {
805 var buf1: [32]u8 = undefined;
806 const value: f64 = 9.999960e-40;
807 const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
808 assert(mem.eql(u8, result, "f64: 9.99996e-40\n"));
809 }
810 {
811 var buf1: [32]u8 = undefined;
812 const value: f64 = 1.409706e-42;
813 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
814 assert(mem.eql(u8, result, "f64: 1.40971e-42\n"));
815 }
816 {
817 var buf1: [32]u8 = undefined;
818 const value: f64 = @bitCast(f32, u32(814313563));
819 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
820 assert(mem.eql(u8, result, "f64: 1.00000e-09\n"));
821 }
822 {
823 var buf1: [32]u8 = undefined;
824 const value: f64 = @bitCast(f32, u32(1006632960));
825 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
826 assert(mem.eql(u8, result, "f64: 7.81250e-03\n"));
827 }
828 {
829 // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
830 // In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
831 var buf1: [32]u8 = undefined;
832 const value: f64 = @bitCast(f32, u32(1203982400));
833 const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
834 assert(mem.eql(u8, result, "f64: 1.00001e+05\n"));
612 }835 }
613 {836 {
614 var buf1: [32]u8 = undefined;837 var buf1: [32]u8 = undefined;
615 const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);838 const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
616 assert(mem.eql(u8, result, "f64: NaN\n"));839 assert(mem.eql(u8, result, "f64: nan\n"));
840 }
841 {
842 var buf1: [32]u8 = undefined;
843 const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64);
844 assert(mem.eql(u8, result, "f64: -nan\n"));
617 }845 }
618 {846 {
619 var buf1: [32]u8 = undefined;847 var buf1: [32]u8 = undefined;
620 const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);848 const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
621 assert(mem.eql(u8, result, "f64: Infinity\n"));849 assert(mem.eql(u8, result, "f64: inf\n"));
622 }850 }
623 {851 {
624 var buf1: [32]u8 = undefined;852 var buf1: [32]u8 = undefined;
625 const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);853 const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
626 assert(mem.eql(u8, result, "f64: -Infinity\n"));854 assert(mem.eql(u8, result, "f64: -inf\n"));
855 }
856 {
857 var buf1: [64]u8 = undefined;
858 const value: f64 = 1.52314e+29;
859 const result = try bufPrint(buf1[0..], "f64: {.}\n", value);
860 assert(mem.eql(u8, result, "f64: 152314000000000000000000000000\n"));
627 }861 }
628 {862 {
629 var buf1: [32]u8 = undefined;863 var buf1: [32]u8 = undefined;
...@@ -635,20 +869,20 @@ test "fmt.format" {...@@ -635,20 +869,20 @@ test "fmt.format" {
635 var buf1: [32]u8 = undefined;869 var buf1: [32]u8 = undefined;
636 const value: f32 = 1234.567;870 const value: f32 = 1234.567;
637 const result = try bufPrint(buf1[0..], "f32: {.2}\n", value);871 const result = try bufPrint(buf1[0..], "f32: {.2}\n", value);
638 assert(mem.eql(u8, result, "f32: 1234.56\n"));872 assert(mem.eql(u8, result, "f32: 1234.57\n"));
639 }873 }
640 {874 {
641 var buf1: [32]u8 = undefined;875 var buf1: [32]u8 = undefined;
642 const value: f32 = -11.1234;876 const value: f32 = -11.1234;
643 const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);877 const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);
644 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).878 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
645 // -11.12339... is truncated to -11.1233879 // -11.12339... is rounded back up to -11.1234
646 assert(mem.eql(u8, result, "f32: -11.1233\n"));880 assert(mem.eql(u8, result, "f32: -11.1234\n"));
647 }881 }
648 {882 {
649 var buf1: [32]u8 = undefined;883 var buf1: [32]u8 = undefined;
650 const value: f32 = 91.12345;884 const value: f32 = 91.12345;
651 const result = try bufPrint(buf1[0..], "f32: {.}\n", value);885 const result = try bufPrint(buf1[0..], "f32: {.5}\n", value);
652 assert(mem.eql(u8, result, "f32: 91.12345\n"));886 assert(mem.eql(u8, result, "f32: 91.12345\n"));
653 }887 }
654 {888 {
...@@ -657,7 +891,100 @@ test "fmt.format" {...@@ -657,7 +891,100 @@ test "fmt.format" {
657 const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);891 const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
658 assert(mem.eql(u8, result, "f64: 91.1234567890\n"));892 assert(mem.eql(u8, result, "f64: 91.1234567890\n"));
659 }893 }
894 {
895 var buf1: [32]u8 = undefined;
896 const value: f64 = 0.0;
897 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
898 assert(mem.eql(u8, result, "f64: 0.00000\n"));
899 }
900 {
901 var buf1: [32]u8 = undefined;
902 const value: f64 = 5.700;
903 const result = try bufPrint(buf1[0..], "f64: {.0}\n", value);
904 assert(mem.eql(u8, result, "f64: 6\n"));
905 }
906 {
907 var buf1: [32]u8 = undefined;
908 const value: f64 = 9.999;
909 const result = try bufPrint(buf1[0..], "f64: {.1}\n", value);
910 assert(mem.eql(u8, result, "f64: 10.0\n"));
911 }
912 {
913 var buf1: [32]u8 = undefined;
914 const value: f64 = 1.0;
915 const result = try bufPrint(buf1[0..], "f64: {.3}\n", value);
916 assert(mem.eql(u8, result, "f64: 1.000\n"));
917 }
918 {
919 var buf1: [32]u8 = undefined;
920 const value: f64 = 0.0003;
921 const result = try bufPrint(buf1[0..], "f64: {.8}\n", value);
922 assert(mem.eql(u8, result, "f64: 0.00030000\n"));
923 }
924 {
925 var buf1: [32]u8 = undefined;
926 const value: f64 = 1.40130e-45;
927 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
928 assert(mem.eql(u8, result, "f64: 0.00000\n"));
929 }
930 {
931 var buf1: [32]u8 = undefined;
932 const value: f64 = 9.999960e-40;
933 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
934 assert(mem.eql(u8, result, "f64: 0.00000\n"));
935 }
936 // libc checks
937 {
938 var buf1: [32]u8 = undefined;
939 const value: f64 = f64(@bitCast(f32, u32(916964781)));
940 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
941 assert(mem.eql(u8, result, "f64: 0.00001\n"));
942 }
943 {
944 var buf1: [32]u8 = undefined;
945 const value: f64 = f64(@bitCast(f32, u32(925353389)));
946 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
947 assert(mem.eql(u8, result, "f64: 0.00001\n"));
948 }
949 {
950 var buf1: [32]u8 = undefined;
951 const value: f64 = f64(@bitCast(f32, u32(1036831278)));
952 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
953 assert(mem.eql(u8, result, "f64: 0.10000\n"));
954 }
955 {
956 var buf1: [32]u8 = undefined;
957 const value: f64 = f64(@bitCast(f32, u32(1065353133)));
958 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
959 assert(mem.eql(u8, result, "f64: 1.00000\n"));
960 }
961 {
962 var buf1: [32]u8 = undefined;
963 const value: f64 = f64(@bitCast(f32, u32(1092616192)));
964 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
965 assert(mem.eql(u8, result, "f64: 10.00000\n"));
966 }
967 // libc differences
968 {
969 var buf1: [32]u8 = undefined;
970 // This is 0.015625 exactly according to gdb. We thus round down,
971 // however glibc rounds up for some reason. This occurs for all
972 // floats of the form x.yyyy25 on a precision point.
973 const value: f64 = f64(@bitCast(f32, u32(1015021568)));
974 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
975 assert(mem.eql(u8, result, "f64: 0.01563\n"));
976 }
660977
978 // std-windows-x86_64-Debug-bare test case fails
979 {
980 // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
981 // also rounds to 630 so I'm inclined to believe libc is not
982 // optimal here.
983 var buf1: [32]u8 = undefined;
984 const value: f64 = f64(@bitCast(f32, u32(1518338049)));
985 const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
986 assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));
987 }
661 }988 }
662}989}
663990