| author | |
| committer | |
| log | 7ef64ccf55927f9e1c5b01687907c870095c8e45 |
| tree | 48b9789942004b17eb5c94b876e7cc505f4c4c06 |
| parent | 11c8c38332ada0585a8690e84c8de3affb34c935 |
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 qNaN | 168 | // 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 the | 174 | // 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; |
| 327 | 328 | ||
| ... | @@ -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 exact | 356 | // a underflows on conversion to the destination type or is an exact |
| 356 | // zero. The result may be a denormal or zero. Extract the exponent | 357 | // 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; |
| 359 | 360 | ||
| 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 | } |
| 137 | 137 | ||
| 138 | fn 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 | |||
| 143 | fn 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 | |||
| 148 | fn 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 | |||
| 153 | test "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 | |||
| 138 | fn test_f32_floatCast_f128(a: f128, expected: u32) !void { | 167 | fn test_f32_floatCast_f128(a: f128, expected: u32) !void { |
| 139 | const x = f32_floatCast_f128(a); | 168 | const x = f32_floatCast_f128(a); |
| 140 | 169 | ||
| ... | @@ -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 | } |
| 286 | 315 | ||
| 316 | fn 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 | |||
| 287 | test f80_floatCast_f128 { | 322 | test 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 | } |