From 60c2972c5d976e2dc8bf2432c8ab06edcf5c6d35 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 May 2022 15:02:06 -0700 Subject: [PATCH] stage2: fix comptime fixed-width float division --- lib/std/special/compiler_rt/fmod.zig | 49 +++++++++++++++++++--------- src/Sema.zig | 44 ++++++++++++++++--------- test/behavior/floatop.zig | 21 ++++++++++++ 3 files changed, 82 insertions(+), 32 deletions(-) diff --git a/lib/std/special/compiler_rt/fmod.zig b/lib/std/special/compiler_rt/fmod.zig index b9a5710b9c..28e0df3d6b 100644 --- a/lib/std/special/compiler_rt/fmod.zig +++ b/lib/std/special/compiler_rt/fmod.zig @@ -324,25 +324,42 @@ inline fn generic_fmod(comptime T: type, x: T, y: T) T { return @bitCast(T, ux); } -test "fmod, fmodf" { - inline for ([_]type{ f32, f64 }) |T| { - const nan_val = math.nan(T); - const inf_val = math.inf(T); +test "fmodf" { + const nan_val = math.nan(f32); + const inf_val = math.inf(f32); - try std.testing.expect(math.isNan(generic_fmod(T, nan_val, 1.0))); - try std.testing.expect(math.isNan(generic_fmod(T, 1.0, nan_val))); - try std.testing.expect(math.isNan(generic_fmod(T, inf_val, 1.0))); - try std.testing.expect(math.isNan(generic_fmod(T, 0.0, 0.0))); - try std.testing.expect(math.isNan(generic_fmod(T, 1.0, 0.0))); + try std.testing.expect(math.isNan(fmodf(nan_val, 1.0))); + try std.testing.expect(math.isNan(fmodf(1.0, nan_val))); + try std.testing.expect(math.isNan(fmodf(inf_val, 1.0))); + try std.testing.expect(math.isNan(fmodf(0.0, 0.0))); + try std.testing.expect(math.isNan(fmodf(1.0, 0.0))); - try std.testing.expectEqual(@as(T, 0.0), generic_fmod(T, 0.0, 2.0)); - try std.testing.expectEqual(@as(T, -0.0), generic_fmod(T, -0.0, 2.0)); + try std.testing.expectEqual(@as(f32, 0.0), fmodf(0.0, 2.0)); + try std.testing.expectEqual(@as(f32, -0.0), fmodf(-0.0, 2.0)); - try std.testing.expectEqual(@as(T, -2.0), generic_fmod(T, -32.0, 10.0)); - try std.testing.expectEqual(@as(T, -2.0), generic_fmod(T, -32.0, -10.0)); - try std.testing.expectEqual(@as(T, 2.0), generic_fmod(T, 32.0, 10.0)); - try std.testing.expectEqual(@as(T, 2.0), generic_fmod(T, 32.0, -10.0)); - } + try std.testing.expectEqual(@as(f32, -2.0), fmodf(-32.0, 10.0)); + try std.testing.expectEqual(@as(f32, -2.0), fmodf(-32.0, -10.0)); + try std.testing.expectEqual(@as(f32, 2.0), fmodf(32.0, 10.0)); + try std.testing.expectEqual(@as(f32, 2.0), fmodf(32.0, -10.0)); +} + +test "fmod" { + const nan_val = math.nan(f64); + const inf_val = math.inf(f64); + + try std.testing.expect(math.isNan(fmod(nan_val, 1.0))); + try std.testing.expect(math.isNan(fmod(1.0, nan_val))); + try std.testing.expect(math.isNan(fmod(inf_val, 1.0))); + try std.testing.expect(math.isNan(fmod(0.0, 0.0))); + try std.testing.expect(math.isNan(fmod(1.0, 0.0))); + + try std.testing.expectEqual(@as(f64, 0.0), fmod(0.0, 2.0)); + try std.testing.expectEqual(@as(f64, -0.0), fmod(-0.0, 2.0)); + + try std.testing.expectEqual(@as(f64, -2.0), fmod(-32.0, 10.0)); + try std.testing.expectEqual(@as(f64, -2.0), fmod(-32.0, -10.0)); + try std.testing.expectEqual(@as(f64, 2.0), fmod(32.0, 10.0)); + try std.testing.expectEqual(@as(f64, 2.0), fmod(32.0, -10.0)); } test { diff --git a/src/Sema.zig b/src/Sema.zig index b17ca031c2..012750e429 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9847,25 +9847,37 @@ fn analyzeArithmetic( // TODO: emit runtime safety for division by zero // // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. + // If the rhs is zero: + // * comptime_float: compile error for division by zero. + // * other float type: + // * if the lhs is zero: QNaN + // * otherwise: +Inf or -Inf depending on lhs sign + // If the rhs is undefined: + // * comptime_float: compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // * other float type: result is undefined // If the lhs is undefined, result is undefined. - if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(resolved_type, Value.zero); + switch (scalar_tag) { + .Int, .ComptimeInt, .ComptimeFloat => { + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(resolved_type, Value.zero); + } + } } - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (rhs_val.compareWithZero(.eq)) { - return sema.failWithDivideByZero(block, rhs_src); - } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + }, + else => {}, } + if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index cc978f3b8d..ab87283627 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -687,3 +687,24 @@ test "f128 at compile time is lossy" { try expect(@as(f128, 10384593717069655257060992658440192.0) + 1 == 10384593717069655257060992658440192.0); } + +test "comptime fixed-width float zero divided by zero produces NaN" { + inline for (.{ f16, f32, f64, f80, f128 }) |F| { + try expect(math.isNan(@as(F, 0) / @as(F, 0))); + } +} + +test "comptime fixed-width float non-zero divided by zero produces signed Inf" { + inline for (.{ f16, f32, f64, f80, f128 }) |F| { + const pos = @as(F, 1) / @as(F, 0); + const neg = @as(F, -1) / @as(F, 0); + try expect(math.isInf(pos)); + try expect(math.isInf(neg)); + try expect(pos > 0); + try expect(neg < 0); + } +} + +test "comptime_float zero divided by zero produces zero" { + try expect((0.0 / 0.0) == 0.0); +}