Commit 60c2972c5d

Andrew Kelley <andrew@ziglang.org>
2022-05-02 00:02:06
stage2: fix comptime fixed-width float division
1 parent 615a983
Changed files (3)
lib
std
special
compiler_rt
src
test
behavior
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);
-
-        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.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(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));
-    }
+test "fmodf" {
+    const nan_val = math.nan(f32);
+    const inf_val = math.inf(f32);
+
+    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(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(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 {
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()) {
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);
+}