From 7ef64ccf55927f9e1c5b01687907c870095c8e45 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Fri, 23 Oct 2026 00:02:36 -0500 Subject: [PATCH] compiler-rt: fix f80 conversions for subnormal destination The conversion was discarding f80's explicit leading significand bit and using incorrect shift and sticky-bit calculations. Now we preserve that bit and correct the rounding logic. Also ensures f128 NaNs remain NaNs when converted to f80, even when their payload is truncated away, rather than becoming infinity. --- lib/compiler_rt/truncf.zig | 9 ++++---- lib/compiler_rt/truncf_test.zig | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/compiler_rt/truncf.zig b/lib/compiler_rt/truncf.zig index cd8e8cc2bd5088d71b9471d8112bf4266ec6d02a..d2b82e6bba5a743269f5c5bf219fd6aeaaf719eb 100644 --- a/lib/compiler_rt/truncf.zig +++ b/lib/compiler_rt/truncf.zig @@ -168,7 +168,7 @@ pub fn f80_floatCast_f128(a: f128) f80 { // Conjure the result by beginning with infinity, setting the qNaN // bit and inserting the (truncated) trailing NaN field. res.exp = 0x7fff; - res.fraction = 0x8000000000000000; + res.fraction = integer_bit | (integer_bit >> 1); res.fraction |= @as(u64, @truncate(a_abs >> (src_sig_bits - dst_sig_bits))); } else { // The exponent of a is within the range of normal numbers in the @@ -322,6 +322,7 @@ inline fn trunc_f80(comptime dst_t: type, a: f80) dst_t { var a_rep = std.math.F80.fromFloat(a); const sign = a_rep.exp & 0x8000; a_rep.exp &= 0x7FFF; + const significand = a_rep.fraction; a_rep.fraction &= 0x7FFFFFFFFFFFFFFF; var abs_result: dst_rep_t = undefined; @@ -355,14 +356,14 @@ inline fn trunc_f80(comptime dst_t: type, a: f80) dst_t { // a underflows on conversion to the destination type or is an exact // zero. The result may be a denormal or zero. Extract the exponent // to get the shift amount for the denormalization. - const shift = src_exp_bias - dst_exp_bias - a_rep.exp; + const shift = src_exp_bias - dst_exp_bias - a_rep.exp + 1; // Right shift by the denormalization amount with sticky. if (shift > src_sig_bits) { abs_result = 0; } else { - const sticky = @intFromBool(a_rep.fraction << @intCast(shift) != 0); - const denormalized_significand = (a_rep.fraction >> @intCast(shift)) | sticky; + const sticky = @intFromBool(significand << @intCast(src_sig_bits + 1 - shift) != 0); + const denormalized_significand = (significand >> @intCast(shift)) | sticky; abs_result = @intCast(denormalized_significand >> (src_sig_bits - dst_sig_bits)); const round_bits = denormalized_significand & round_mask; if (round_bits > halfway) { diff --git a/lib/compiler_rt/truncf_test.zig b/lib/compiler_rt/truncf_test.zig index 6595c47b52d8f868d019c1728ae3d57735e12323..78d1caa99c024c211d46b57518266119c2911fc4 100644 --- a/lib/compiler_rt/truncf_test.zig +++ b/lib/compiler_rt/truncf_test.zig @@ -135,6 +135,35 @@ test f16_floatCast_f64 { try test_f16_floatCast_f64(65536.0, 0x7c00); } +fn test_f16_floatCast_f80(a: f80, expected: u16) !void { + const actual: u16 = @bitCast(f16_floatCast_f80(a)); + try testing.expectEqual(expected, actual); +} + +fn test_f32_floatCast_f80(a: f80, expected: u32) !void { + const actual: u32 = @bitCast(f32_floatCast_f80(a)); + try testing.expectEqual(expected, actual); +} + +fn test_f64_floatCast_f80(a: f80, expected: u64) !void { + const actual: u64 = @bitCast(f64_floatCast_f80(a)); + try testing.expectEqual(expected, actual); +} + +test "f80 conversions preserve destination subnormals" { + try test_f16_floatCast_f80(0x1p-24, 0x0001); + try test_f16_floatCast_f80(0x1.8p-24, 0x0002); + try test_f16_floatCast_f80(0x1.8p-20, 0x0018); + try test_f16_floatCast_f80(0x1p-17, 0x0080); + try test_f16_floatCast_f80(0x1.fep-15, 0x03fc); + + try test_f32_floatCast_f80(0x1p-149, 0x00000001); + try test_f32_floatCast_f80(0x1p-147, 0x00000004); + + try test_f64_floatCast_f80(0x1p-1074, 0x0000000000000001); + try test_f64_floatCast_f80(0x1.8p-1073, 0x0000000000000003); +} + fn test_f32_floatCast_f128(a: f128, expected: u32) !void { const x = f32_floatCast_f128(a); @@ -284,9 +313,17 @@ fn test_f80_floatCast_f128(a: f128, expected: f80) !void { try testing.expect(x == expected); } +fn test_f80_floatCast_f128_bits(a: u128, expected_exp: u16, expected_fraction: u64) !void { + const actual = std.math.F80.fromFloat(f80_floatCast_f128(@bitCast(a))); + try testing.expectEqual(expected_exp, actual.exp); + try testing.expectEqual(expected_fraction, actual.fraction); +} + test f80_floatCast_f128 { try test_f80_floatCast_f128(1.5, 1.5); try test_f80_floatCast_f128(2.5, 2.5); try test_f80_floatCast_f128(-2.5, -2.5); try test_f80_floatCast_f128(0.0, 0.0); + + try test_f80_floatCast_f128_bits(0x7fff0000000000000000000000000001, 0x7fff, 0xc000000000000000); } -- 2.54.0