Commit 8ff01f78f3

Harrison McCarty <hmccarty@pm.me>
2024-06-30 13:12:52
std.fmt.parseFloat: add f80 formatFloat support
1 parent 959d227
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,
     };
 }
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,
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;
     }
 
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 };
 }
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)
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);
     }
 }
 
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