authorgravatar for david@vortan.devDavid Rubin <david@vortan.dev> 2026-10-23 00:02:36-05:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-24 13:42:01+02:00
log7ef64ccf55927f9e1c5b01687907c870095c8e45
tree48b9789942004b17eb5c94b876e7cc505f4c4c06
parent11c8c38332ada0585a8690e84c8de3affb34c935

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.

2 files changed, 42 insertions(+), 4 deletions(-)

lib/compiler_rt/truncf.zig+5-4
......@@ -168,7 +168,7 @@ pub fn f80_floatCast_f128(a: f128) f80 {
168168 // Conjure the result by beginning with infinity, setting the qNaN
169169 // bit and inserting the (truncated) trailing NaN field.
170170 res.exp = 0x7fff;
171 res.fraction = 0x8000000000000000;
171 res.fraction = integer_bit | (integer_bit >> 1);
172172 res.fraction |= @as(u64, @truncate(a_abs >> (src_sig_bits - dst_sig_bits)));
173173 } else {
174174 // 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 {
322322 var a_rep = std.math.F80.fromFloat(a);
323323 const sign = a_rep.exp & 0x8000;
324324 a_rep.exp &= 0x7FFF;
325 const significand = a_rep.fraction;
325326 a_rep.fraction &= 0x7FFFFFFFFFFFFFFF;
326327 var abs_result: dst_rep_t = undefined;
327328
......@@ -355,14 +356,14 @@ inline fn trunc_f80(comptime dst_t: type, a: f80) dst_t {
355356 // a underflows on conversion to the destination type or is an exact
356357 // zero. The result may be a denormal or zero. Extract the exponent
357358 // to get the shift amount for the denormalization.
358 const shift = src_exp_bias - dst_exp_bias - a_rep.exp;
359 const shift = src_exp_bias - dst_exp_bias - a_rep.exp + 1;
359360
360361 // Right shift by the denormalization amount with sticky.
361362 if (shift > src_sig_bits) {
362363 abs_result = 0;
363364 } else {
364 const sticky = @intFromBool(a_rep.fraction << @intCast(shift) != 0);
365 const denormalized_significand = (a_rep.fraction >> @intCast(shift)) | sticky;
365 const sticky = @intFromBool(significand << @intCast(src_sig_bits + 1 - shift) != 0);
366 const denormalized_significand = (significand >> @intCast(shift)) | sticky;
366367 abs_result = @intCast(denormalized_significand >> (src_sig_bits - dst_sig_bits));
367368 const round_bits = denormalized_significand & round_mask;
368369 if (round_bits > halfway) {
lib/compiler_rt/truncf_test.zig+37
......@@ -135,6 +135,35 @@ test f16_floatCast_f64 {
135135 try test_f16_floatCast_f64(65536.0, 0x7c00);
136136}
137137
138fn test_f16_floatCast_f80(a: f80, expected: u16) !void {
139 const actual: u16 = @bitCast(f16_floatCast_f80(a));
140 try testing.expectEqual(expected, actual);
141}
142
143fn test_f32_floatCast_f80(a: f80, expected: u32) !void {
144 const actual: u32 = @bitCast(f32_floatCast_f80(a));
145 try testing.expectEqual(expected, actual);
146}
147
148fn test_f64_floatCast_f80(a: f80, expected: u64) !void {
149 const actual: u64 = @bitCast(f64_floatCast_f80(a));
150 try testing.expectEqual(expected, actual);
151}
152
153test "f80 conversions preserve destination subnormals" {
154 try test_f16_floatCast_f80(0x1p-24, 0x0001);
155 try test_f16_floatCast_f80(0x1.8p-24, 0x0002);
156 try test_f16_floatCast_f80(0x1.8p-20, 0x0018);
157 try test_f16_floatCast_f80(0x1p-17, 0x0080);
158 try test_f16_floatCast_f80(0x1.fep-15, 0x03fc);
159
160 try test_f32_floatCast_f80(0x1p-149, 0x00000001);
161 try test_f32_floatCast_f80(0x1p-147, 0x00000004);
162
163 try test_f64_floatCast_f80(0x1p-1074, 0x0000000000000001);
164 try test_f64_floatCast_f80(0x1.8p-1073, 0x0000000000000003);
165}
166
138167fn test_f32_floatCast_f128(a: f128, expected: u32) !void {
139168 const x = f32_floatCast_f128(a);
140169
......@@ -284,9 +313,17 @@ fn test_f80_floatCast_f128(a: f128, expected: f80) !void {
284313 try testing.expect(x == expected);
285314}
286315
316fn test_f80_floatCast_f128_bits(a: u128, expected_exp: u16, expected_fraction: u64) !void {
317 const actual = std.math.F80.fromFloat(f80_floatCast_f128(@bitCast(a)));
318 try testing.expectEqual(expected_exp, actual.exp);
319 try testing.expectEqual(expected_fraction, actual.fraction);
320}
321
287322test f80_floatCast_f128 {
288323 try test_f80_floatCast_f128(1.5, 1.5);
289324 try test_f80_floatCast_f128(2.5, 2.5);
290325 try test_f80_floatCast_f128(-2.5, -2.5);
291326 try test_f80_floatCast_f128(0.0, 0.0);
327
328 try test_f80_floatCast_f128_bits(0x7fff0000000000000000000000000001, 0x7fff, 0xc000000000000000);
292329}