stage2: fix comptime fixed-width float division

This commit is contained in:
Andrew Kelley 2022-05-01 15:02:06 -07:00
parent 615a983517
commit 60c2972c5d
3 changed files with 82 additions and 32 deletions

View File

@ -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 {

View File

@ -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()) {

View File

@ -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);
}