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 {...@@ -168,7 +168,7 @@ pub fn f80_floatCast_f128(a: f128) f80 {
168 // Conjure the result by beginning with infinity, setting the qNaN168 // Conjure the result by beginning with infinity, setting the qNaN
169 // bit and inserting the (truncated) trailing NaN field.169 // bit and inserting the (truncated) trailing NaN field.
170 res.exp = 0x7fff;170 res.exp = 0x7fff;
171 res.fraction = 0x8000000000000000;171 res.fraction = integer_bit | (integer_bit >> 1);
172 res.fraction |= @as(u64, @truncate(a_abs >> (src_sig_bits - dst_sig_bits)));172 res.fraction |= @as(u64, @truncate(a_abs >> (src_sig_bits - dst_sig_bits)));
173 } else {173 } else {
174 // The exponent of a is within the range of normal numbers in the174 // 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 {...@@ -322,6 +322,7 @@ inline fn trunc_f80(comptime dst_t: type, a: f80) dst_t {
322 var a_rep = std.math.F80.fromFloat(a);322 var a_rep = std.math.F80.fromFloat(a);
323 const sign = a_rep.exp & 0x8000;323 const sign = a_rep.exp & 0x8000;
324 a_rep.exp &= 0x7FFF;324 a_rep.exp &= 0x7FFF;
325 const significand = a_rep.fraction;
325 a_rep.fraction &= 0x7FFFFFFFFFFFFFFF;326 a_rep.fraction &= 0x7FFFFFFFFFFFFFFF;
326 var abs_result: dst_rep_t = undefined;327 var abs_result: dst_rep_t = undefined;
327328
...@@ -355,14 +356,14 @@ inline fn trunc_f80(comptime dst_t: type, a: f80) dst_t {...@@ -355,14 +356,14 @@ inline fn trunc_f80(comptime dst_t: type, a: f80) dst_t {
355 // a underflows on conversion to the destination type or is an exact356 // a underflows on conversion to the destination type or is an exact
356 // zero. The result may be a denormal or zero. Extract the exponent357 // zero. The result may be a denormal or zero. Extract the exponent
357 // to get the shift amount for the denormalization.358 // 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
360 // Right shift by the denormalization amount with sticky.361 // Right shift by the denormalization amount with sticky.
361 if (shift > src_sig_bits) {362 if (shift > src_sig_bits) {
362 abs_result = 0;363 abs_result = 0;
363 } else {364 } else {
364 const sticky = @intFromBool(a_rep.fraction << @intCast(shift) != 0);365 const sticky = @intFromBool(significand << @intCast(src_sig_bits + 1 - shift) != 0);
365 const denormalized_significand = (a_rep.fraction >> @intCast(shift)) | sticky;366 const denormalized_significand = (significand >> @intCast(shift)) | sticky;
366 abs_result = @intCast(denormalized_significand >> (src_sig_bits - dst_sig_bits));367 abs_result = @intCast(denormalized_significand >> (src_sig_bits - dst_sig_bits));
367 const round_bits = denormalized_significand & round_mask;368 const round_bits = denormalized_significand & round_mask;
368 if (round_bits > halfway) {369 if (round_bits > halfway) {
lib/compiler_rt/truncf_test.zig+37
...@@ -135,6 +135,35 @@ test f16_floatCast_f64 {...@@ -135,6 +135,35 @@ test f16_floatCast_f64 {
135 try test_f16_floatCast_f64(65536.0, 0x7c00);135 try test_f16_floatCast_f64(65536.0, 0x7c00);
136}136}
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
138fn test_f32_floatCast_f128(a: f128, expected: u32) !void {167fn test_f32_floatCast_f128(a: f128, expected: u32) !void {
139 const x = f32_floatCast_f128(a);168 const x = f32_floatCast_f128(a);
140169
...@@ -284,9 +313,17 @@ fn test_f80_floatCast_f128(a: f128, expected: f80) !void {...@@ -284,9 +313,17 @@ fn test_f80_floatCast_f128(a: f128, expected: f80) !void {
284 try testing.expect(x == expected);313 try testing.expect(x == expected);
285}314}
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
287test f80_floatCast_f128 {322test f80_floatCast_f128 {
288 try test_f80_floatCast_f128(1.5, 1.5);323 try test_f80_floatCast_f128(1.5, 1.5);
289 try test_f80_floatCast_f128(2.5, 2.5);324 try test_f80_floatCast_f128(2.5, 2.5);
290 try test_f80_floatCast_f128(-2.5, -2.5);325 try test_f80_floatCast_f128(-2.5, -2.5);
291 try test_f80_floatCast_f128(0.0, 0.0);326 try test_f80_floatCast_f128(0.0, 0.0);
327
328 try test_f80_floatCast_f128_bits(0x7fff0000000000000000000000000001, 0x7fff, 0xc000000000000000);
292}329}