From 8ff01f78f314a743df8c8f085a02416bb1ce0c72 Mon Sep 17 00:00:00 2001 From: Harrison McCarty Date: Sun, 30 Jun 2024 04:12:52 -0700 Subject: [PATCH] std.fmt.parseFloat: add f80 formatFloat support --- lib/std/fmt/format_float.zig | 14 +++++----- lib/std/fmt/parse_float.zig | 33 +++++++++++++++++------- lib/std/fmt/parse_float/FloatInfo.zig | 26 ++++++++++++++++--- lib/std/fmt/parse_float/common.zig | 9 +++++-- lib/std/fmt/parse_float/convert_fast.zig | 7 +++++ lib/std/fmt/parse_float/convert_hex.zig | 11 ++++---- lib/std/fmt/parse_float/convert_slow.zig | 12 ++++----- 7 files changed, 79 insertions(+), 33 deletions(-) diff --git a/lib/std/fmt/format_float.zig b/lib/std/fmt/format_float.zig index 0207efa4ff..d0ae7624e4 100644 --- a/lib/std/fmt/format_float.zig +++ b/lib/std/fmt/format_float.zig @@ -1521,15 +1521,13 @@ fn check(comptime T: type, value: T, comptime expected: []const u8) !void { const s = try formatFloat(&buf, value, .{}); try std.testing.expectEqualStrings(expected, s); - if (@bitSizeOf(T) != 80) { - const o = try std.fmt.parseFloat(T, s); - const o_bits: I = @bitCast(o); + const o = try std.fmt.parseFloat(T, s); + const o_bits: I = @bitCast(o); - if (std.math.isNan(value)) { - try std.testing.expect(std.math.isNan(o)); - } else { - try std.testing.expectEqual(value_bits, o_bits); - } + if (std.math.isNan(value)) { + try std.testing.expect(std.math.isNan(o)); + } else { + try std.testing.expectEqual(value_bits, o_bits); } } diff --git a/lib/std/fmt/parse_float.zig b/lib/std/fmt/parse_float.zig index 7e22477617..425286ff07 100644 --- a/lib/std/fmt/parse_float.zig +++ b/lib/std/fmt/parse_float.zig @@ -21,10 +21,6 @@ pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T { @compileError("Cannot parse a float into a non-floating point type."); } - if (T == f80) { - @compileError("TODO support parsing float to f80"); - } - if (s.len == 0) { return error.InvalidCharacter; } @@ -75,7 +71,7 @@ pub fn parseFloat(comptime T: type, s: []const u8) ParseFloatError!T { // See https://github.com/tiehuis/parse-number-fxx-test-data for a wider-selection of test-data. test parseFloat { - inline for ([_]type{ f16, f32, f64, f128 }) |T| { + inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { try testing.expectError(error.InvalidCharacter, parseFloat(T, "")); try testing.expectError(error.InvalidCharacter, parseFloat(T, " 1")); try testing.expectError(error.InvalidCharacter, parseFloat(T, "1abc")); @@ -131,7 +127,7 @@ test parseFloat { } test "nan and inf" { - inline for ([_]type{ f16, f32, f64, f128 }) |T| { + inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| { const Z = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); try expectEqual(@as(Z, @bitCast(try parseFloat(T, "nAn"))), @as(Z, @bitCast(std.math.nan(T)))); @@ -144,6 +140,7 @@ test "largest normals" { try expectEqual(@as(u16, @bitCast(try parseFloat(f16, "65504"))), 0x7bff); try expectEqual(@as(u32, @bitCast(try parseFloat(f32, "3.4028234664E38"))), 0x7f7f_ffff); try expectEqual(@as(u64, @bitCast(try parseFloat(f64, "1.7976931348623157E308"))), 0x7fef_ffff_ffff_ffff); + try expectEqual(@as(u80, @bitCast(try parseFloat(f80, "1.189731495357231765E4932"))), 0x7ffe_ffff_ffff_ffff_ffff); try expectEqual(@as(u128, @bitCast(try parseFloat(f128, "1.1897314953572317650857593266280070162E4932"))), 0x7ffe_ffff_ffff_ffff_ffff_ffff_ffff_ffff); } @@ -152,8 +149,8 @@ test "#11169" { } test "many_digits hex" { - const a: f32 = try std.fmt.parseFloat(f32, "0xffffffffffffffff.0p0"); - const b: f32 = @floatCast(try std.fmt.parseFloat(f128, "0xffffffffffffffff.0p0")); + const a: f32 = try parseFloat(f32, "0xffffffffffffffff.0p0"); + const b: f32 = @floatCast(try parseFloat(f128, "0xffffffffffffffff.0p0")); try std.testing.expectEqual(a, b); } @@ -163,6 +160,7 @@ test "hex.special" { try testing.expect(math.isPositiveInf(try parseFloat(f32, "+Inf"))); try testing.expect(math.isNegativeInf(try parseFloat(f32, "-iNf"))); } + test "hex.zero" { try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "0x0")); try testing.expectEqual(@as(f32, 0.0), try parseFloat(f32, "-0x0")); @@ -221,6 +219,23 @@ test "hex.f64" { try testing.expectEqual(try parseFloat(f64, "0x1p-1074"), math.floatTrueMin(f64)); try testing.expectEqual(try parseFloat(f64, "-0x1p-1074"), -math.floatTrueMin(f64)); } + +test "hex.f80" { + try testing.expectEqual(try parseFloat(f80, "0x1p0"), 1.0); + try testing.expectEqual(try parseFloat(f80, "-0x1p-1"), -0.5); + try testing.expectEqual(try parseFloat(f80, "0x10p+10"), 16384.0); + try testing.expectEqual(try parseFloat(f80, "0x10p-10"), 0.015625); + // Max normalized value. + try testing.expectEqual(try parseFloat(f80, "0xf.fffffffffffffff7p+16380"), math.floatMax(f80)); + try testing.expectEqual(try parseFloat(f80, "-0xf.fffffffffffffff7p+16380"), -math.floatMax(f80)); + // Min normalized value. + try testing.expectEqual(try parseFloat(f80, "0x1p-16382"), math.floatMin(f80)); + try testing.expectEqual(try parseFloat(f80, "-0x1p-16382"), -math.floatMin(f80)); + // Min denormalized value. + try testing.expectEqual(try parseFloat(f80, "0x1p-16445"), math.floatTrueMin(f80)); + try testing.expectEqual(try parseFloat(f80, "-0x1p-16445"), -math.floatTrueMin(f80)); +} + test "hex.f128" { try testing.expectEqual(try parseFloat(f128, "0x1p0"), 1.0); try testing.expectEqual(try parseFloat(f128, "-0x1p-1"), -0.5); @@ -232,7 +247,7 @@ test "hex.f128" { // Min normalized value. try testing.expectEqual(try parseFloat(f128, "0x1p-16382"), math.floatMin(f128)); try testing.expectEqual(try parseFloat(f128, "-0x1p-16382"), -math.floatMin(f128)); - // // Min denormalized value. + // Min denormalized value. try testing.expectEqual(try parseFloat(f128, "0x1p-16494"), math.floatTrueMin(f128)); try testing.expectEqual(try parseFloat(f128, "-0x1p-16494"), -math.floatTrueMin(f128)); // ensure round-to-even diff --git a/lib/std/fmt/parse_float/FloatInfo.zig b/lib/std/fmt/parse_float/FloatInfo.zig index 8fd95963b1..e6343c4bdf 100644 --- a/lib/std/fmt/parse_float/FloatInfo.zig +++ b/lib/std/fmt/parse_float/FloatInfo.zig @@ -60,7 +60,7 @@ pub fn from(comptime T: type) Self { .max_exponent_fast_path_disguised = 7, .max_mantissa_fast_path = 2 << std.math.floatMantissaBits(T), // Slow + Eisel-Lemire - .mantissa_explicit_bits = std.math.floatMantissaBits(T), + .mantissa_explicit_bits = std.math.floatFractionalBits(T), .infinite_power = 0x1f, // Eisel-Lemire .smallest_power_of_ten = -26, // TODO: refine, fails one test @@ -81,7 +81,7 @@ pub fn from(comptime T: type) Self { .max_exponent_fast_path_disguised = 17, .max_mantissa_fast_path = 2 << std.math.floatMantissaBits(T), // Slow + Eisel-Lemire - .mantissa_explicit_bits = std.math.floatMantissaBits(T), + .mantissa_explicit_bits = std.math.floatFractionalBits(T), .infinite_power = 0xff, // Eisel-Lemire .smallest_power_of_ten = -65, @@ -106,6 +106,26 @@ pub fn from(comptime T: type) Self { .min_exponent_round_to_even = -4, .max_exponent_round_to_even = 23, }, + f80 => .{ + // Fast-Path + .min_exponent_fast_path = -27, + .max_exponent_fast_path = 27, + .max_exponent_fast_path_disguised = 46, + .max_mantissa_fast_path = 2 << std.math.floatMantissaBits(T), + // Slow + Eisel-Lemire + .mantissa_explicit_bits = std.math.floatFractionalBits(T), + .infinite_power = 0x7fff, + // Eisel-Lemire. + // NOTE: Not yet tested (no f80 eisel-lemire implementation) + .smallest_power_of_ten = -4966, + .largest_power_of_ten = 4932, + .minimum_exponent = -16382, + // 2^65 * 5^-q < 2^80 + // 5^-q < 2^15 + // => q >= -6 + .min_exponent_round_to_even = -6, + .max_exponent_round_to_even = 28, + }, f128 => .{ // Fast-Path .min_exponent_fast_path = -48, @@ -113,7 +133,7 @@ pub fn from(comptime T: type) Self { .max_exponent_fast_path_disguised = 82, .max_mantissa_fast_path = 2 << std.math.floatMantissaBits(T), // Slow + Eisel-Lemire - .mantissa_explicit_bits = std.math.floatMantissaBits(T), + .mantissa_explicit_bits = std.math.floatFractionalBits(T), .infinite_power = 0x7fff, // Eisel-Lemire. // NOTE: Not yet tested (no f128 eisel-lemire implementation) diff --git a/lib/std/fmt/parse_float/common.zig b/lib/std/fmt/parse_float/common.zig index 8dba3b4498..4c33a9081d 100644 --- a/lib/std/fmt/parse_float/common.zig +++ b/lib/std/fmt/parse_float/common.zig @@ -23,7 +23,11 @@ pub fn BiasedFp(comptime T: type) type { } pub fn inf(comptime FloatT: type) Self { - return .{ .f = 0, .e = (1 << std.math.floatExponentBits(FloatT)) - 1 }; + const e = (1 << std.math.floatExponentBits(FloatT)) - 1; + return switch (FloatT) { + f80 => .{ .f = 0x8000000000000000, .e = e }, + else => .{ .f = 0, .e = e }, + }; } pub fn eql(self: Self, other: Self) bool { @@ -45,6 +49,7 @@ pub fn floatFromUnsigned(comptime T: type, comptime MantissaT: type, v: Mantissa f16 => @as(f16, @bitCast(@as(u16, @truncate(v)))), f32 => @as(f32, @bitCast(@as(u32, @truncate(v)))), f64 => @as(f64, @bitCast(@as(u64, @truncate(v)))), + f80 => @as(f80, @bitCast(@as(u80, @truncate(v)))), f128 => @as(f128, @bitCast(v)), else => unreachable, }; @@ -85,7 +90,7 @@ pub fn isDigit(c: u8, comptime base: u8) bool { pub fn mantissaType(comptime T: type) type { return switch (T) { f16, f32, f64 => u64, - f128 => u128, + f80, f128 => u128, else => unreachable, }; } diff --git a/lib/std/fmt/parse_float/convert_fast.zig b/lib/std/fmt/parse_float/convert_fast.zig index a148d3946f..262bf96fbf 100644 --- a/lib/std/fmt/parse_float/convert_fast.zig +++ b/lib/std/fmt/parse_float/convert_fast.zig @@ -46,6 +46,13 @@ fn fastPow10(comptime T: type, i: usize) T { 0, 0, 0, 0, 0, 0, 0, 0, })[i & 31], + f80 => ([32]f80{ + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, + 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, + 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, + 1e24, 1e25, 1e26, 1e27, 0, 0, 0, 0, + })[i & 31], + f128 => ([64]f128{ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, diff --git a/lib/std/fmt/parse_float/convert_hex.zig b/lib/std/fmt/parse_float/convert_hex.zig index 815331347c..d2063f1c00 100644 --- a/lib/std/fmt/parse_float/convert_hex.zig +++ b/lib/std/fmt/parse_float/convert_hex.zig @@ -25,11 +25,12 @@ pub fn convertHex(comptime T: type, n_: Number(T)) T { const max_exp = math.floatExponentMax(T); const min_exp = math.floatExponentMin(T); const mantissa_bits = math.floatMantissaBits(T); + const fractional_bits = math.floatFractionalBits(T); const exp_bits = math.floatExponentBits(T); const exp_bias = min_exp - 1; - // mantissa now implicitly divided by 2^mantissa_bits - n.exponent += mantissa_bits; + // mantissa now implicitly divided by 2^fractional_bits + n.exponent += fractional_bits; // Shift mantissa and exponent to bring representation into float range. // Eventually we want a mantissa with a leading 1-bit followed by mantbits other bits. @@ -44,7 +45,7 @@ pub fn convertHex(comptime T: type, n_: Number(T)) T { if (n.many_digits) { n.mantissa |= 1; } - while (n.mantissa >> (1 + mantissa_bits + 2) != 0) { + while (n.mantissa >> (1 + fractional_bits + 2) != 0) { n.mantissa = (n.mantissa >> 1) | (n.mantissa & 1); n.exponent += 1; } @@ -64,14 +65,14 @@ pub fn convertHex(comptime T: type, n_: Number(T)) T { n.exponent += 2; if (round == 3) { n.mantissa += 1; - if (n.mantissa == 1 << (1 + mantissa_bits)) { + if (n.mantissa == 1 << (1 + fractional_bits)) { n.mantissa >>= 1; n.exponent += 1; } } // Denormal or zero - if (n.mantissa >> mantissa_bits == 0) { + if (n.mantissa >> fractional_bits == 0) { n.exponent = exp_bias; } diff --git a/lib/std/fmt/parse_float/convert_slow.zig b/lib/std/fmt/parse_float/convert_slow.zig index 357253c03e..e4539937bd 100644 --- a/lib/std/fmt/parse_float/convert_slow.zig +++ b/lib/std/fmt/parse_float/convert_slow.zig @@ -41,7 +41,7 @@ pub fn convertSlow(comptime T: type, s: []const u8) BiasedFp(T) { const MantissaT = mantissaType(T); const min_exponent = -(1 << (math.floatExponentBits(T) - 1)) + 1; const infinite_power = (1 << math.floatExponentBits(T)) - 1; - const mantissa_explicit_bits = math.floatMantissaBits(T); + const fractional_bits = math.floatFractionalBits(T); var d = Decimal(T).parse(s); // no need to recheck underscores if (d.num_digits == 0 or d.decimal_point < Decimal(T).min_exponent) { @@ -97,9 +97,9 @@ pub fn convertSlow(comptime T: type, s: []const u8) BiasedFp(T) { // Shift the decimal to the hidden bit, and then round the value // to get the high mantissa+1 bits. - d.leftShift(mantissa_explicit_bits + 1); + d.leftShift(fractional_bits + 1); var mantissa = d.round(); - if (mantissa >= (@as(MantissaT, 1) << (mantissa_explicit_bits + 1))) { + if (mantissa >= (@as(MantissaT, 1) << (fractional_bits + 1))) { // Rounding up overflowed to the carry bit, need to // shift back to the hidden bit. d.rightShift(1); @@ -110,10 +110,10 @@ pub fn convertSlow(comptime T: type, s: []const u8) BiasedFp(T) { } } var power2 = exp2 - min_exponent; - if (mantissa < (@as(MantissaT, 1) << mantissa_explicit_bits)) { + if (mantissa < (@as(MantissaT, 1) << fractional_bits)) { power2 -= 1; } - // Zero out all the bits above the explicit mantissa bits. - mantissa &= (@as(MantissaT, 1) << mantissa_explicit_bits) - 1; + // Zero out all the bits above the mantissa bits. + mantissa &= (@as(MantissaT, 1) << math.floatMantissaBits(T)) - 1; return .{ .f = mantissa, .e = power2 }; }