diff options
| author | LemonBoy <thatlemon@gmail.com> | 2021-03-23 10:08:34 +0100 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-05-05 12:37:40 -0400 |
| commit | 9d409233b2c62e1d24635165ac6b3b7686ffd5e4 (patch) | |
| tree | e8a03a66155664457f65f282f2701594aa048dde /lib/std/fmt.zig | |
| parent | 18b46485bcb6ad6b4be31c87659ffd78a311c904 (diff) | |
| download | zig-9d409233b2c62e1d24635165ac6b3b7686ffd5e4.tar.gz zig-9d409233b2c62e1d24635165ac6b3b7686ffd5e4.zip | |
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.
Diffstat (limited to 'lib/std/fmt.zig')
| -rw-r--r-- | lib/std/fmt.zig | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 491a1d0726..878969b88a 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -696,6 +696,11 @@ fn formatFloatValue( error.NoSpaceLeft => unreachable, else => |e| return e, }; + } else if (comptime std.mem.eql(u8, fmt, "x")) { + formatFloatHexadecimal(value, options, buf_stream.writer()) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + else => |e| return e, + }; } else { @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); } @@ -1023,6 +1028,112 @@ pub fn formatFloatScientific( } } +pub fn formatFloatHexadecimal( + value: anytype, + options: FormatOptions, + writer: anytype, +) !void { + if (math.signbit(value)) { + try writer.writeByte('-'); + } + if (math.isNan(value)) { + return writer.writeAll("nan"); + } + if (math.isInf(value)) { + return writer.writeAll("inf"); + } + + const T = @TypeOf(value); + const TU = std.meta.Int(.unsigned, std.meta.bitCount(T)); + + const mantissa_bits = math.floatMantissaBits(T); + const exponent_bits = math.floatExponentBits(T); + const mantissa_mask = (1 << mantissa_bits) - 1; + const exponent_mask = (1 << exponent_bits) - 1; + const exponent_bias = (1 << (exponent_bits - 1)) - 1; + + const as_bits = @bitCast(TU, value); + var mantissa = as_bits & mantissa_mask; + var exponent: i32 = @truncate(u16, (as_bits >> mantissa_bits) & exponent_mask); + + const is_denormal = exponent == 0 and mantissa != 0; + const is_zero = exponent == 0 and mantissa == 0; + + if (is_zero) { + // Handle this case here to simplify the logic below. + try writer.writeAll("0x0"); + if (options.precision) |precision| { + if (precision > 0) { + try writer.writeAll("."); + try writer.writeByteNTimes('0', precision); + } + } else { + try writer.writeAll(".0"); + } + try writer.writeAll("p0"); + return; + } + + if (is_denormal) { + // Adjust the exponent for printing. + exponent += 1; + } else { + // Add the implicit 1. + mantissa |= 1 << mantissa_bits; + } + + // Fill in zeroes to round the mantissa width to a multiple of 4. + if (T == f16) mantissa <<= 2 else if (T == f32) mantissa <<= 1; + + const mantissa_digits = (mantissa_bits + 3) / 4; + + if (options.precision) |precision| { + // Round if needed. + if (precision < mantissa_digits) { + // We always have at least 4 extra bits. + var extra_bits = (mantissa_digits - precision) * 4; + // The result LSB is the Guard bit, we need two more (Round and + // Sticky) to round the value. + while (extra_bits > 2) { + mantissa = (mantissa >> 1) | (mantissa & 1); + extra_bits -= 1; + } + // Round to nearest, tie to even. + mantissa |= @boolToInt(mantissa & 0b100 != 0); + mantissa += 1; + // Drop the excess bits. + mantissa >>= 2; + // Restore the alignment. + mantissa <<= @intCast(math.Log2Int(TU), (mantissa_digits - precision) * 4); + + const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; + // Prefer a normalized result in case of overflow. + if (overflow) { + mantissa >>= 1; + exponent += 1; + } + } + } + + // +1 for the decimal part. + var buf: [1 + mantissa_digits]u8 = undefined; + const N = formatIntBuf(&buf, mantissa, 16, false, .{ .fill = '0', .width = 1 + mantissa_digits }); + + try writer.writeAll("0x"); + try writer.writeByte(buf[0]); + if (options.precision != @as(usize, 0)) + try writer.writeAll("."); + const trimmed = mem.trimRight(u8, buf[1..], "0"); + try writer.writeAll(trimmed); + // Add trailing zeros if explicitly requested. + if (options.precision) |precision| if (precision > 0) { + if (precision > trimmed.len) + try writer.writeByteNTimes('0', precision - trimmed.len); + }; + try writer.writeAll("p"); + try formatInt(exponent - exponent_bias, 10, false, .{}, writer); +} + /// Print a float of the format x.yyyyy where the number of y is specified by the precision argument. /// By default floats are printed at full precision (no rounding). pub fn formatFloatDecimal( @@ -1900,6 +2011,54 @@ test "float.special" { try expectFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); } +test "float.hexadecimal.special" { + try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64}); + // negative nan is not defined by IEE 754, + // and ARM thus normalizes it to positive nan + if (builtin.arch != builtin.Arch.arm) { + try expectFmt("f64: -nan", "f64: {x}", .{-math.nan_f64}); + } + try expectFmt("f64: inf", "f64: {x}", .{math.inf_f64}); + try expectFmt("f64: -inf", "f64: {x}", .{-math.inf_f64}); + + try expectFmt("f64: 0x0.0p0", "f64: {x}", .{@as(f64, 0)}); + try expectFmt("f64: -0x0.0p0", "f64: {x}", .{-@as(f64, 0)}); +} + +test "float.hexadecimal" { + try expectFmt("f16: 0x1.554p-2", "f16: {x}", .{@as(f16, 1.0 / 3.0)}); + try expectFmt("f32: 0x1.555556p-2", "f32: {x}", .{@as(f32, 1.0 / 3.0)}); + try expectFmt("f64: 0x1.5555555555555p-2", "f64: {x}", .{@as(f64, 1.0 / 3.0)}); + try expectFmt("f128: 0x1.5555555555555555555555555555p-2", "f128: {x}", .{@as(f128, 1.0 / 3.0)}); + + try expectFmt("f16: 0x1.p-14", "f16: {x}", .{@as(f16, math.f16_min)}); + try expectFmt("f32: 0x1.p-126", "f32: {x}", .{@as(f32, math.f32_min)}); + try expectFmt("f64: 0x1.p-1022", "f64: {x}", .{@as(f64, math.f64_min)}); + try expectFmt("f128: 0x1.p-16382", "f128: {x}", .{@as(f128, math.f128_min)}); + + try expectFmt("f16: 0x0.004p-14", "f16: {x}", .{@as(f16, math.f16_true_min)}); + try expectFmt("f32: 0x0.000002p-126", "f32: {x}", .{@as(f32, math.f32_true_min)}); + try expectFmt("f64: 0x0.0000000000001p-1022", "f64: {x}", .{@as(f64, math.f64_true_min)}); + try expectFmt("f128: 0x0.0000000000000000000000000001p-16382", "f128: {x}", .{@as(f128, math.f128_true_min)}); + + try expectFmt("f16: 0x1.ffcp15", "f16: {x}", .{@as(f16, math.f16_max)}); + try expectFmt("f32: 0x1.fffffep127", "f32: {x}", .{@as(f32, math.f32_max)}); + try expectFmt("f64: 0x1.fffffffffffffp1023", "f64: {x}", .{@as(f64, math.f64_max)}); + try expectFmt("f128: 0x1.ffffffffffffffffffffffffffffp16383", "f128: {x}", .{@as(f128, math.f128_max)}); +} + +test "float.hexadecimal.precision" { + try expectFmt("f16: 0x1.5p-2", "f16: {x:.1}", .{@as(f16, 1.0 / 3.0)}); + try expectFmt("f32: 0x1.555p-2", "f32: {x:.3}", .{@as(f32, 1.0 / 3.0)}); + try expectFmt("f64: 0x1.55555p-2", "f64: {x:.5}", .{@as(f64, 1.0 / 3.0)}); + try expectFmt("f128: 0x1.5555555p-2", "f128: {x:.7}", .{@as(f128, 1.0 / 3.0)}); + + try expectFmt("f16: 0x1.00000p0", "f16: {x:.5}", .{@as(f16, 1.0)}); + try expectFmt("f32: 0x1.00000p0", "f32: {x:.5}", .{@as(f32, 1.0)}); + try expectFmt("f64: 0x1.00000p0", "f64: {x:.5}", .{@as(f64, 1.0)}); + try expectFmt("f128: 0x1.00000p0", "f128: {x:.5}", .{@as(f128, 1.0)}); +} + test "float.decimal" { try expectFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); try expectFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); |
