authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2021-03-23 10:08:34+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-05 12:37:40-04:00
log9d409233b2c62e1d24635165ac6b3b7686ffd5e4
treee8a03a66155664457f65f282f2701594aa048dde
parent18b46485bcb6ad6b4be31c87659ffd78a311c904

std: Implement hex float printing

The results have been cross-checked with LLVM's APFloat implementation by randomly sampling the f32/f64 space, while the f16 one was completely checked given the small size.

1 files changed, 159 insertions(+), 0 deletions(-)

lib/std/fmt.zig+159
......@@ -696,6 +696,11 @@ fn formatFloatValue(
696696 error.NoSpaceLeft => unreachable,
697697 else => |e| return e,
698698 };
699 } else if (comptime std.mem.eql(u8, fmt, "x")) {
700 formatFloatHexadecimal(value, options, buf_stream.writer()) catch |err| switch (err) {
701 error.NoSpaceLeft => unreachable,
702 else => |e| return e,
703 };
699704 } else {
700705 @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
701706 }
......@@ -1023,6 +1028,112 @@ pub fn formatFloatScientific(
10231028 }
10241029}
10251030
1031pub fn formatFloatHexadecimal(
1032 value: anytype,
1033 options: FormatOptions,
1034 writer: anytype,
1035) !void {
1036 if (math.signbit(value)) {
1037 try writer.writeByte('-');
1038 }
1039 if (math.isNan(value)) {
1040 return writer.writeAll("nan");
1041 }
1042 if (math.isInf(value)) {
1043 return writer.writeAll("inf");
1044 }
1045
1046 const T = @TypeOf(value);
1047 const TU = std.meta.Int(.unsigned, std.meta.bitCount(T));
1048
1049 const mantissa_bits = math.floatMantissaBits(T);
1050 const exponent_bits = math.floatExponentBits(T);
1051 const mantissa_mask = (1 << mantissa_bits) - 1;
1052 const exponent_mask = (1 << exponent_bits) - 1;
1053 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1054
1055 const as_bits = @bitCast(TU, value);
1056 var mantissa = as_bits & mantissa_mask;
1057 var exponent: i32 = @truncate(u16, (as_bits >> mantissa_bits) & exponent_mask);
1058
1059 const is_denormal = exponent == 0 and mantissa != 0;
1060 const is_zero = exponent == 0 and mantissa == 0;
1061
1062 if (is_zero) {
1063 // Handle this case here to simplify the logic below.
1064 try writer.writeAll("0x0");
1065 if (options.precision) |precision| {
1066 if (precision > 0) {
1067 try writer.writeAll(".");
1068 try writer.writeByteNTimes('0', precision);
1069 }
1070 } else {
1071 try writer.writeAll(".0");
1072 }
1073 try writer.writeAll("p0");
1074 return;
1075 }
1076
1077 if (is_denormal) {
1078 // Adjust the exponent for printing.
1079 exponent += 1;
1080 } else {
1081 // Add the implicit 1.
1082 mantissa |= 1 << mantissa_bits;
1083 }
1084
1085 // Fill in zeroes to round the mantissa width to a multiple of 4.
1086 if (T == f16) mantissa <<= 2 else if (T == f32) mantissa <<= 1;
1087
1088 const mantissa_digits = (mantissa_bits + 3) / 4;
1089
1090 if (options.precision) |precision| {
1091 // Round if needed.
1092 if (precision < mantissa_digits) {
1093 // We always have at least 4 extra bits.
1094 var extra_bits = (mantissa_digits - precision) * 4;
1095 // The result LSB is the Guard bit, we need two more (Round and
1096 // Sticky) to round the value.
1097 while (extra_bits > 2) {
1098 mantissa = (mantissa >> 1) | (mantissa & 1);
1099 extra_bits -= 1;
1100 }
1101 // Round to nearest, tie to even.
1102 mantissa |= @boolToInt(mantissa & 0b100 != 0);
1103 mantissa += 1;
1104 // Drop the excess bits.
1105 mantissa >>= 2;
1106 // Restore the alignment.
1107 mantissa <<= @intCast(math.Log2Int(TU), (mantissa_digits - precision) * 4);
1108
1109 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1110 // Prefer a normalized result in case of overflow.
1111 if (overflow) {
1112 mantissa >>= 1;
1113 exponent += 1;
1114 }
1115 }
1116 }
1117
1118 // +1 for the decimal part.
1119 var buf: [1 + mantissa_digits]u8 = undefined;
1120 const N = formatIntBuf(&buf, mantissa, 16, false, .{ .fill = '0', .width = 1 + mantissa_digits });
1121
1122 try writer.writeAll("0x");
1123 try writer.writeByte(buf[0]);
1124 if (options.precision != @as(usize, 0))
1125 try writer.writeAll(".");
1126 const trimmed = mem.trimRight(u8, buf[1..], "0");
1127 try writer.writeAll(trimmed);
1128 // Add trailing zeros if explicitly requested.
1129 if (options.precision) |precision| if (precision > 0) {
1130 if (precision > trimmed.len)
1131 try writer.writeByteNTimes('0', precision - trimmed.len);
1132 };
1133 try writer.writeAll("p");
1134 try formatInt(exponent - exponent_bias, 10, false, .{}, writer);
1135}
1136
10261137/// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
10271138/// By default floats are printed at full precision (no rounding).
10281139pub fn formatFloatDecimal(
......@@ -1900,6 +2011,54 @@ test "float.special" {
19002011 try expectFmt("f64: -inf", "f64: {}", .{-math.inf_f64});
19012012}
19022013
2014test "float.hexadecimal.special" {
2015 try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64});
2016 // negative nan is not defined by IEE 754,
2017 // and ARM thus normalizes it to positive nan
2018 if (builtin.arch != builtin.Arch.arm) {
2019 try expectFmt("f64: -nan", "f64: {x}", .{-math.nan_f64});
2020 }
2021 try expectFmt("f64: inf", "f64: {x}", .{math.inf_f64});
2022 try expectFmt("f64: -inf", "f64: {x}", .{-math.inf_f64});
2023
2024 try expectFmt("f64: 0x0.0p0", "f64: {x}", .{@as(f64, 0)});
2025 try expectFmt("f64: -0x0.0p0", "f64: {x}", .{-@as(f64, 0)});
2026}
2027
2028test "float.hexadecimal" {
2029 try expectFmt("f16: 0x1.554p-2", "f16: {x}", .{@as(f16, 1.0 / 3.0)});
2030 try expectFmt("f32: 0x1.555556p-2", "f32: {x}", .{@as(f32, 1.0 / 3.0)});
2031 try expectFmt("f64: 0x1.5555555555555p-2", "f64: {x}", .{@as(f64, 1.0 / 3.0)});
2032 try expectFmt("f128: 0x1.5555555555555555555555555555p-2", "f128: {x}", .{@as(f128, 1.0 / 3.0)});
2033
2034 try expectFmt("f16: 0x1.p-14", "f16: {x}", .{@as(f16, math.f16_min)});
2035 try expectFmt("f32: 0x1.p-126", "f32: {x}", .{@as(f32, math.f32_min)});
2036 try expectFmt("f64: 0x1.p-1022", "f64: {x}", .{@as(f64, math.f64_min)});
2037 try expectFmt("f128: 0x1.p-16382", "f128: {x}", .{@as(f128, math.f128_min)});
2038
2039 try expectFmt("f16: 0x0.004p-14", "f16: {x}", .{@as(f16, math.f16_true_min)});
2040 try expectFmt("f32: 0x0.000002p-126", "f32: {x}", .{@as(f32, math.f32_true_min)});
2041 try expectFmt("f64: 0x0.0000000000001p-1022", "f64: {x}", .{@as(f64, math.f64_true_min)});
2042 try expectFmt("f128: 0x0.0000000000000000000000000001p-16382", "f128: {x}", .{@as(f128, math.f128_true_min)});
2043
2044 try expectFmt("f16: 0x1.ffcp15", "f16: {x}", .{@as(f16, math.f16_max)});
2045 try expectFmt("f32: 0x1.fffffep127", "f32: {x}", .{@as(f32, math.f32_max)});
2046 try expectFmt("f64: 0x1.fffffffffffffp1023", "f64: {x}", .{@as(f64, math.f64_max)});
2047 try expectFmt("f128: 0x1.ffffffffffffffffffffffffffffp16383", "f128: {x}", .{@as(f128, math.f128_max)});
2048}
2049
2050test "float.hexadecimal.precision" {
2051 try expectFmt("f16: 0x1.5p-2", "f16: {x:.1}", .{@as(f16, 1.0 / 3.0)});
2052 try expectFmt("f32: 0x1.555p-2", "f32: {x:.3}", .{@as(f32, 1.0 / 3.0)});
2053 try expectFmt("f64: 0x1.55555p-2", "f64: {x:.5}", .{@as(f64, 1.0 / 3.0)});
2054 try expectFmt("f128: 0x1.5555555p-2", "f128: {x:.7}", .{@as(f128, 1.0 / 3.0)});
2055
2056 try expectFmt("f16: 0x1.00000p0", "f16: {x:.5}", .{@as(f16, 1.0)});
2057 try expectFmt("f32: 0x1.00000p0", "f32: {x:.5}", .{@as(f32, 1.0)});
2058 try expectFmt("f64: 0x1.00000p0", "f64: {x:.5}", .{@as(f64, 1.0)});
2059 try expectFmt("f128: 0x1.00000p0", "f128: {x:.5}", .{@as(f128, 1.0)});
2060}
2061
19032062test "float.decimal" {
19042063 try expectFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)});
19052064 try expectFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)});