| ... | ... | @@ -8,6 +8,7 @@ |
| 8 | 8 | const std = @import("std"); |
| 9 | 9 | const math = std.math; |
| 10 | 10 | const expect = std.testing.expect; |
| 11 | const expectEqual = std.testing.expectEqual; |
| 11 | 12 | const compiler_rt = @import("../compiler_rt.zig"); |
| 12 | 13 | const symbol = compiler_rt.symbol; |
| 13 | 14 | |
| ... | ... | @@ -40,8 +41,10 @@ pub fn fma_f32(x: f32, y: f32, z: f32) f32 { |
| 40 | 41 | if ((u & 0x1FFFFFFF) != 0x10000000 or e == 0x7FF or (xy_z - xy == z and xy_z - z == xy)) { |
| 41 | 42 | return @floatCast(xy_z); |
| 42 | 43 | } else { |
| 43 | | // TODO: Handle inexact case with double-rounding |
| 44 | | return @floatCast(xy_z); |
| 44 | // Correct double rounding at an f32 midpoint. |
| 45 | const negative = u >> 63 != 0; |
| 46 | const err = if (negative == (z > xy)) xy - xy_z + z else z - xy_z + xy; |
| 47 | return @floatCast(@as(f64, @bitCast(if (negative == (err < 0)) u + 1 else u - 1))); |
| 45 | 48 | } |
| 46 | 49 | } |
| 47 | 50 | |
| ... | ... | @@ -74,6 +77,7 @@ pub fn fma_f64(x: f64, y: f64, z: f64) f64 { |
| 74 | 77 | var zs = x3.significand; |
| 75 | 78 | |
| 76 | 79 | var spread = ex + ey - ez; |
| 80 | if (spread < -53) return z; |
| 77 | 81 | if (spread <= 53 * 2) { |
| 78 | 82 | zs = math.scalbn(zs, -spread); |
| 79 | 83 | } else { |
| ... | ... | @@ -85,7 +89,7 @@ pub fn fma_f64(x: f64, y: f64, z: f64) f64 { |
| 85 | 89 | spread = ex + ey; |
| 86 | 90 | |
| 87 | 91 | if (r.hi == 0.0) { |
| 88 | | return xy.hi + zs + math.scalbn(xy.lo, spread); |
| 92 | return if (xy.lo == 0.0) xy.hi + zs else math.scalbn(xy.lo, spread); |
| 89 | 93 | } |
| 90 | 94 | |
| 91 | 95 | const adj = add_adjusted(r.lo, xy.lo); |
| ... | ... | @@ -139,6 +143,7 @@ pub fn fma_f128(x: f128, y: f128, z: f128) f128 { |
| 139 | 143 | var zs = x3.significand; |
| 140 | 144 | |
| 141 | 145 | var spread = ex + ey - ez; |
| 146 | if (spread < -113) return z; |
| 142 | 147 | if (spread <= 113 * 2) { |
| 143 | 148 | zs = math.scalbn(zs, -spread); |
| 144 | 149 | } else { |
| ... | ... | @@ -150,7 +155,7 @@ pub fn fma_f128(x: f128, y: f128, z: f128) f128 { |
| 150 | 155 | spread = ex + ey; |
| 151 | 156 | |
| 152 | 157 | if (r.hi == 0.0) { |
| 153 | | return xy.hi + zs + math.scalbn(xy.lo, spread); |
| 158 | return if (xy.lo == 0.0) xy.hi + zs else math.scalbn(xy.lo, spread); |
| 154 | 159 | } |
| 155 | 160 | |
| 156 | 161 | const adj = add_adjusted128(r.lo, xy.lo); |
| ... | ... | @@ -224,7 +229,7 @@ fn add_and_denorm(a: f64, b: f64, scale: i32) f64 { |
| 224 | 229 | if (sum.lo != 0) { |
| 225 | 230 | var uhii: u64 = @bitCast(sum.hi); |
| 226 | 231 | const bits_lost = -@as(i32, @intCast((uhii >> 52) & 0x7FF)) - scale + 1; |
| 227 | | if ((bits_lost != 1) == (uhii & 1 != 0)) { |
| 232 | if ((bits_lost != 1) != (uhii & 1 != 0)) { |
| 228 | 233 | const uloi: u64 = @bitCast(sum.lo); |
| 229 | 234 | uhii = uhii + 1 - (((uhii ^ uloi) >> 62) & 2); |
| 230 | 235 | sum.hi = @bitCast(uhii); |
| ... | ... | @@ -291,7 +296,7 @@ fn add_and_denorm128(a: f128, b: f128, scale: i32) f128 { |
| 291 | 296 | if (sum.lo != 0) { |
| 292 | 297 | var uhii: u128 = @bitCast(sum.hi); |
| 293 | 298 | const bits_lost = -@as(i32, @intCast((uhii >> 112) & 0x7FFF)) - scale + 1; |
| 294 | | if ((bits_lost != 1) == (uhii & 1 != 0)) { |
| 299 | if ((bits_lost != 1) != (uhii & 1 != 0)) { |
| 295 | 300 | const uloi: u128 = @bitCast(sum.lo); |
| 296 | 301 | uhii = uhii + 1 - (((uhii ^ uloi) >> 126) & 2); |
| 297 | 302 | sum.hi = @bitCast(uhii); |
| ... | ... | @@ -335,6 +340,9 @@ test "32" { |
| 335 | 340 | try expect(math.approxEqAbs(f32, fma_f32(37.45, 5.0, 9.124), 196.374004, epsilon)); |
| 336 | 341 | try expect(math.approxEqAbs(f32, fma_f32(89.123, 5.0, 9.124), 454.739005, epsilon)); |
| 337 | 342 | try expect(math.approxEqAbs(f32, fma_f32(123123.234375, 5.0, 9.124), 615625.295875, epsilon)); |
| 343 | |
| 344 | // Avoid double rounding when the f64 sum lands on an f32 midpoint. |
| 345 | try expectEqual(0x3fc00001, @as(u32, @bitCast(fma_f32(0x1.000002p0, 1.5, -0x1p-60)))); |
| 338 | 346 | } |
| 339 | 347 | |
| 340 | 348 | test "64" { |
| ... | ... | @@ -347,6 +355,45 @@ test "64" { |
| 347 | 355 | try expect(math.approxEqAbs(f64, fma_f64(37.45, 5.0, 9.124), 196.374, epsilon)); |
| 348 | 356 | try expect(math.approxEqAbs(f64, fma_f64(89.123, 5.0, 9.124), 454.739, epsilon)); |
| 349 | 357 | try expect(math.approxEqAbs(f64, fma_f64(123123.234375, 5.0, 9.124), 615625.295875, epsilon)); |
| 358 | |
| 359 | // Special operands. |
| 360 | try expect(math.isNan(fma_f64(math.inf(f64), 0.0, 1.0))); |
| 361 | try expect(math.isNan(fma_f64(math.inf(f64), 1.0, -math.inf(f64)))); |
| 362 | try expectEqual(math.inf(f64), fma_f64(2.0, 3.0, math.inf(f64))); |
| 363 | try expectEqual(math.inf(f64), fma_f64(math.floatMax(f64), 2.0, 0.0)); |
| 364 | try expect(math.isNan(fma_f64(math.nan(f64), 1.0, 1.0))); |
| 365 | |
| 366 | // Addend dominates product. |
| 367 | try expectEqual(-1.438e308, fma_f64(-0.6, math.floatTrueMin(f64), -1.438e308)); |
| 368 | try expectEqual(1.0, fma_f64(0x1p-600, 0x1p-600, 1.0)); |
| 369 | try expectEqual(math.floatMax(f64), fma_f64(math.floatTrueMin(f64), math.floatTrueMin(f64), math.floatMax(f64))); |
| 370 | |
| 371 | // Subnormal result is rounded once. |
| 372 | try expectEqual(0x000d2971831e2f43, @as(u64, @bitCast(fma_f64( |
| 373 | @bitCast(@as(u64, 0xbbbcae314558d732)), |
| 374 | @bitCast(@as(u64, 0x843d607d0e518063)), |
| 375 | @bitCast(@as(u64, 0x800000ba24d8f3ba)), |
| 376 | )))); |
| 377 | try expectEqual(0x000516a34ce113eb, @as(u64, @bitCast(fma_f64( |
| 378 | @bitCast(@as(u64, 0x2af4b8a2ad5294f0)), |
| 379 | @bitCast(@as(u64, 0x14ef6dd2b62857c2)), |
| 380 | @bitCast(@as(u64, 0x000000248ff787f4)), |
| 381 | )))); |
| 382 | try expectEqual(0x800bccc9d0e68a53, @as(u64, @bitCast(fma_f64( |
| 383 | @bitCast(@as(u64, 0x3e81368dcd4cb557)), |
| 384 | @bitCast(@as(u64, 0x817514ce659bdad8)), |
| 385 | @bitCast(@as(u64, 0x800075cba3b3043c)), |
| 386 | )))); |
| 387 | |
| 388 | // Sign of zero. |
| 389 | try expectEqual(0x8000000000000000, @as(u64, @bitCast(fma_f64( |
| 390 | @bitCast(@as(u64, 0x2001526ce7a829e5)), |
| 391 | @bitCast(@as(u64, 0xa00c9ac305fd618a)), |
| 392 | @bitCast(@as(u64, 0x001ef7cb10a75631)), |
| 393 | )))); |
| 394 | try expect(!math.signbit(fma_f64(1.0, 1.0, -1.0))); |
| 395 | try expect(math.signbit(fma_f64(-math.floatTrueMin(f64), 0.5, 0.0))); |
| 396 | try expect(math.signbit(fma_f64(-0.0, 1.0, -0.0))); |
| 350 | 397 | } |
| 351 | 398 | |
| 352 | 399 | test "128" { |
| ... | ... | @@ -359,4 +406,26 @@ test "128" { |
| 359 | 406 | try expect(math.approxEqAbs(f128, fma_f128(37.45, 5.0, 9.124), 196.374, epsilon)); |
| 360 | 407 | try expect(math.approxEqAbs(f128, fma_f128(89.123, 5.0, 9.124), 454.739, epsilon)); |
| 361 | 408 | try expect(math.approxEqAbs(f128, fma_f128(123123.234375, 5.0, 9.124), 615625.295875, epsilon)); |
| 409 | |
| 410 | // Addend dominates product. |
| 411 | try expectEqual(-1.0e4932, fma_f128(-0.6, math.floatTrueMin(f128), -1.0e4932)); |
| 412 | try expectEqual(0x8000f2eb9b403234570b698042da365d, @as(u128, @bitCast(fma_f128( |
| 413 | @bitCast(@as(u128, 0xa4d80b69452301dd5a3efab045ca0371)), |
| 414 | @bitCast(@as(u128, 0x13a5efea97a6ece0f0b24ffab5fcdcb4)), |
| 415 | @bitCast(@as(u128, 0x8000f2eb9b403234570b698042da365d)), |
| 416 | )))); |
| 417 | |
| 418 | // Subnormal result is rounded once. |
| 419 | try expectEqual(0x0000f5ea6135ffda377d6f2322102a7b, @as(u128, @bitCast(fma_f128( |
| 420 | @bitCast(@as(u128, 0x2d83ef38da3dc9f883f14ecb887541ce)), |
| 421 | @bitCast(@as(u128, 0x123129a960f91ee507caa6a6702fc33e)), |
| 422 | @bitCast(@as(u128, 0x0000f5ea6135ffda377d6eff2506556b)), |
| 423 | )))); |
| 424 | |
| 425 | // Sign of zero. |
| 426 | try expectEqual(0x80000000000000000000000000000000, @as(u128, @bitCast(fma_f128( |
| 427 | @bitCast(@as(u128, 0x1fd32f92b54d7cefc06df6f8521f1bc3)), |
| 428 | @bitCast(@as(u128, 0x202bb49b61ca93fdb382f78b6a2a32e2)), |
| 429 | @bitCast(@as(u128, 0x8000816f87a4a3a99ed5f1db8e4c0404)), |
| 430 | )))); |
| 362 | 431 | } |