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 {
1212 exp: i32,
1313};
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
1581/// Corrected Errol3 double to ASCII conversion.
1682pub fn errol3(value: f64, buffer: []u8) FloatDecimal {
1783 const bits = @bitCast(u64, value);
1884 const i = tableLowerBound(bits);
1985 if (i < enum3.len and enum3[i] == bits) {
2086 const data = enum3_data[i];
21 const digits = buffer[0..data.str.len];
87 const digits = buffer[1..data.str.len + 1];
2288 mem.copy(u8, digits, data.str);
2389 return FloatDecimal {
2490 .digits = digits,
......@@ -98,7 +164,11 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
98164 }
99165
100166 // 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;
102172 while (true) {
103173 var hdig = u8(math.floor(high.val));
104174 if ((high.val == f64(hdig)) and (high.off < 0))
......@@ -128,7 +198,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
128198 buf_index += 1;
129199
130200 return FloatDecimal {
131 .digits = buffer[0..buf_index],
201 .digits = buffer[1..buf_index],
132202 .exp = exp,
133203 };
134204}
std/fmt/index.zig+383-56
......@@ -4,7 +4,7 @@ const debug = std.debug;
44const assert = debug.assert;
55const mem = std.mem;
66const builtin = @import("builtin");
7const errol3 = @import("errol/index.zig").errol3;
7const errol = @import("errol/index.zig");
88
99const max_int_digits = 65;
1010
......@@ -22,6 +22,8 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
2222 IntegerWidth,
2323 Float,
2424 FloatWidth,
25 FloatScientific,
26 FloatScientificWidth,
2527 Character,
2628 Buf,
2729 BufWidth,
......@@ -87,6 +89,9 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
8789 's' => {
8890 state = State.Buf;
8991 },
92 'e' => {
93 state = State.FloatScientific;
94 },
9095 '.' => {
9196 state = State.Float;
9297 },
......@@ -133,9 +138,33 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
133138 '0' ... '9' => {},
134139 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
135140 },
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 },
136165 State.Float => switch (c) {
137166 '}' => {
138 try formatFloatDecimal(args[next_arg], 0, context, Errors, output);
167 try formatFloatDecimal(args[next_arg], null, context, Errors, output);
139168 next_arg += 1;
140169 state = State.Start;
141170 start_index = i + 1;
......@@ -199,7 +228,7 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
199228 return formatInt(value, 10, false, 0, context, Errors, output);
200229 },
201230 builtin.TypeId.Float => {
202 return formatFloat(value, context, Errors, output);
231 return formatFloatScientific(value, null, context, Errors, output);
203232 },
204233 builtin.TypeId.Void => {
205234 return output(context, "void");
......@@ -257,81 +286,237 @@ pub fn formatBuf(buf: []const u8, width: usize,
257286 }
258287}
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 {
261293 var x = f64(value);
262294
263295 // Errol doesn't handle these special cases.
264 if (math.isNan(x)) {
265 return output(context, "NaN");
266 }
267296 if (math.signbit(x)) {
268297 try output(context, "-");
269298 x = -x;
270299 }
300
301 if (math.isNan(x)) {
302 return output(context, "nan");
303 }
271304 if (math.isPositiveInf(x)) {
272 return output(context, "Infinity");
305 return output(context, "inf");
273306 }
274307 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;
276324 }
277325
278326 var buffer: [32]u8 = undefined;
279 const float_decimal = errol3(x, buffer[0..]);
280 try output(context, float_decimal.digits[0..1]);
281 try output(context, ".");
282 if (float_decimal.digits.len > 1) {
283 const num_digits = if (@typeOf(value) == f32)
284 math.min(usize(9), float_decimal.digits.len)
285 else
286 float_decimal.digits.len;
287 try output(context, float_decimal.digits[1 .. num_digits]);
327 var float_decimal = errol.errol3(x, buffer[0..]);
328
329 if (maybe_precision) |precision| {
330 errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific);
331
332 try output(context, float_decimal.digits[0..1]);
333
334 // {e0} case prints no `.`
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 }
288349 } 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 }
290362 }
291363
292 if (float_decimal.exp != 1) {
293 try output(context, "e");
294 try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output);
364 try output(context, "e");
365 const exp = float_decimal.exp - 1;
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);
295379 }
296380}
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 {
299385 var x = f64(value);
300386
301387 // Errol doesn't handle these special cases.
302 if (math.isNan(x)) {
303 return output(context, "NaN");
304 }
305388 if (math.signbit(x)) {
306389 try output(context, "-");
307390 x = -x;
308391 }
392
393 if (math.isNan(x)) {
394 return output(context, "nan");
395 }
309396 if (math.isPositiveInf(x)) {
310 return output(context, "Infinity");
397 return output(context, "inf");
311398 }
312399 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;
314417 }
315418
419 // non-special case, use errol3
316420 var buffer: [32]u8 = undefined;
317 const float_decimal = errol3(x, buffer[0..]);
318
319 const num_left_digits = if (float_decimal.exp > 0) usize(float_decimal.exp) else 1;
320
321 try output(context, float_decimal.digits[0 .. num_left_digits]);
322 try output(context, ".");
323 if (float_decimal.digits.len > 1) {
324 const num_valid_digtis = if (@typeOf(value) == f32) math.min(usize(7), float_decimal.digits.len)
325 else
326 float_decimal.digits.len;
327
328 const num_right_digits = if (precision != 0)
329 math.min(precision, (num_valid_digtis-num_left_digits))
330 else
331 num_valid_digtis - num_left_digits;
332 try output(context, float_decimal.digits[num_left_digits .. (num_left_digits + num_right_digits)]);
421 var float_decimal = errol.errol3(x, buffer[0..]);
422
423 if (maybe_precision) |precision| {
424 errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
425
426 // exp < 0 means the leading is always 0 as errol result is normalized.
427 var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
428
429 // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
430 var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
431
432 if (num_digits_whole > 0) {
433 // We may have to zero pad, for instance 1e4 requires zero padding.
434 try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
435
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 }
333483 } 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 ..]);
335520 }
336521}
337522
......@@ -598,32 +783,81 @@ test "fmt.format" {
598783 // TODO get these tests passing in release modes
599784 // https://github.com/zig-lang/zig/issues/564
600785 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 }
601792 {
602793 var buf1: [32]u8 = undefined;
603794 const value: f32 = 12.34;
604 const result = try bufPrint(buf1[0..], "f32: {}\n", value);
605 assert(mem.eql(u8, result, "f32: 1.23400001e1\n"));
795 const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
796 assert(mem.eql(u8, result, "f32: 1.23400001e+01\n"));
606797 }
607798 {
608799 var buf1: [32]u8 = undefined;
609800 const value: f64 = -12.34e10;
610 const result = try bufPrint(buf1[0..], "f64: {}\n", value);
611 assert(mem.eql(u8, result, "f64: -1.234e11\n"));
801 const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
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"));
612835 }
613836 {
614837 var buf1: [32]u8 = undefined;
615838 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"));
617845 }
618846 {
619847 var buf1: [32]u8 = undefined;
620848 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"));
622850 }
623851 {
624852 var buf1: [32]u8 = undefined;
625853 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"));
627861 }
628862 {
629863 var buf1: [32]u8 = undefined;
......@@ -635,20 +869,20 @@ test "fmt.format" {
635869 var buf1: [32]u8 = undefined;
636870 const value: f32 = 1234.567;
637871 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"));
639873 }
640874 {
641875 var buf1: [32]u8 = undefined;
642876 const value: f32 = -11.1234;
643877 const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);
644878 // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
645 // -11.12339... is truncated to -11.1233
646 assert(mem.eql(u8, result, "f32: -11.1233\n"));
879 // -11.12339... is rounded back up to -11.1234
880 assert(mem.eql(u8, result, "f32: -11.1234\n"));
647881 }
648882 {
649883 var buf1: [32]u8 = undefined;
650884 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);
652886 assert(mem.eql(u8, result, "f32: 91.12345\n"));
653887 }
654888 {
......@@ -657,7 +891,100 @@ test "fmt.format" {
657891 const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
658892 assert(mem.eql(u8, result, "f64: 91.1234567890\n"));
659893 }
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 }
661988 }
662989}
663990