Skip to content

Commit 8147a26

Browse files
committed
std.math: allow comptime_floats in isInf and isNan
Currently constants passed to these functions trigger an obtuse: `error: access of union field 'float' while field 'comptime_float' is active` where the work-around is to cast the constant into a runtime float. From ziglang#21205, I'm leveraging the fact that `comptime_float` won't (eventually) support NaN or infinity, so the `isNan` and `isInf` methods can unilaterally return false for `comptime_float` parameters. Add tests for comptime floats passed to isNan, isInf*. See ziglang#23258 for the motivation. Fixes ziglang#22107
1 parent 6b6dc1c commit 8147a26

File tree

2 files changed

+55
-20
lines changed

2 files changed

+55
-20
lines changed

lib/std/math/isinf.zig

+34-15
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,38 @@ const std = @import("../std.zig");
22
const math = std.math;
33
const expect = std.testing.expect;
44

5-
/// Returns whether x is an infinity, ignoring sign.
6-
pub inline fn isInf(x: anytype) bool {
5+
/// Returns whether float argument is an infinity, ignoring sign.
6+
pub fn isInf(x: anytype) bool {
77
const T = @TypeOf(x);
8-
const TBits = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
9-
const remove_sign = ~@as(TBits, 0) >> 1;
10-
return @as(TBits, @bitCast(x)) & remove_sign == @as(TBits, @bitCast(math.inf(T)));
8+
switch (@typeInfo(T)) {
9+
.comptime_float => return false, // comptime does not allow inf
10+
.float => {
11+
const TBits = std.meta.Int(.unsigned, @typeInfo(T).float.bits);
12+
const remove_sign = ~@as(TBits, 0) >> 1;
13+
return @as(TBits, @bitCast(x)) & remove_sign == @as(TBits, @bitCast(math.inf(T)));
14+
},
15+
else => @compileError("isInf unsupported for " ++ @typeName(T)),
16+
}
1117
}
1218

13-
/// Returns whether x is an infinity with a positive sign.
14-
pub inline fn isPositiveInf(x: anytype) bool {
15-
return x == math.inf(@TypeOf(x));
19+
/// Returns whether float argument is an infinity with a positive sign.
20+
pub fn isPositiveInf(x: anytype) bool {
21+
const T = @TypeOf(x);
22+
switch (@typeInfo(T)) {
23+
.comptime_float => return false, // comptime does not allow inf
24+
.float => return x == math.inf(T),
25+
else => @compileError("isPositiveInf unsupported for " ++ @typeName(T)),
26+
}
1627
}
1728

18-
/// Returns whether x is an infinity with a negative sign.
19-
pub inline fn isNegativeInf(x: anytype) bool {
20-
return x == -math.inf(@TypeOf(x));
29+
/// Returns whether float argument is an infinity with a negative sign.
30+
pub fn isNegativeInf(x: anytype) bool {
31+
const T = @TypeOf(x);
32+
switch (@typeInfo(T)) {
33+
.comptime_float => return false, // comptime does not allow inf
34+
.float => return x == -math.inf(T),
35+
else => @compileError("isNegativeInf unsupported for " ++ @typeName(T)),
36+
}
2137
}
2238

2339
test isInf {
@@ -29,6 +45,7 @@ test isInf {
2945
try expect(!isInf(math.nan(T)));
3046
try expect(!isInf(-math.nan(T)));
3147
}
48+
try expect(!isInf(0.0));
3249
}
3350

3451
test isPositiveInf {
@@ -37,9 +54,10 @@ test isPositiveInf {
3754
try expect(!isPositiveInf(@as(T, -0.0)));
3855
try expect(isPositiveInf(math.inf(T)));
3956
try expect(!isPositiveInf(-math.inf(T)));
40-
try expect(!isInf(math.nan(T)));
41-
try expect(!isInf(-math.nan(T)));
57+
try expect(!isPositiveInf(math.nan(T)));
58+
try expect(!isPositiveInf(-math.nan(T)));
4259
}
60+
try expect(!isPositiveInf(0.0));
4361
}
4462

4563
test isNegativeInf {
@@ -48,7 +66,8 @@ test isNegativeInf {
4866
try expect(!isNegativeInf(@as(T, -0.0)));
4967
try expect(!isNegativeInf(math.inf(T)));
5068
try expect(isNegativeInf(-math.inf(T)));
51-
try expect(!isInf(math.nan(T)));
52-
try expect(!isInf(-math.nan(T)));
69+
try expect(!isNegativeInf(math.nan(T)));
70+
try expect(!isNegativeInf(-math.nan(T)));
5371
}
72+
try expect(!isNegativeInf(0.0));
5473
}

lib/std/math/isnan.zig

+21-5
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,27 @@ const meta = std.meta;
55
const expect = std.testing.expect;
66

77
pub fn isNan(x: anytype) bool {
8-
return x != x;
8+
const T = @TypeOf(x);
9+
switch (@typeInfo(T)) {
10+
.comptime_float => return false, // comptime does not allow NaN
11+
.float => return x != x,
12+
else => @compileError("isNan unsupported for " ++ @typeName(T)),
13+
}
914
}
1015

1116
/// TODO: LLVM is known to miscompile on some architectures to quiet NaN -
1217
/// this is tracked by https://github.com/ziglang/zig/issues/14366
1318
pub fn isSignalNan(x: anytype) bool {
1419
const T = @TypeOf(x);
15-
const U = meta.Int(.unsigned, @bitSizeOf(T));
16-
const quiet_signal_bit_mask = 1 << (math.floatFractionalBits(T) - 1);
17-
return isNan(x) and (@as(U, @bitCast(x)) & quiet_signal_bit_mask == 0);
20+
switch (@typeInfo(T)) {
21+
.comptime_float => return false, // comptime does not allow NaN
22+
.float => {
23+
const U = meta.Int(.unsigned, @bitSizeOf(T));
24+
const quiet_signal_bit_mask = 1 << (math.floatFractionalBits(T) - 1);
25+
return isNan(x) and (@as(U, @bitCast(x)) & quiet_signal_bit_mask == 0);
26+
},
27+
else => @compileError("isSignalNan unsupported for " ++ @typeName(T)),
28+
}
1829
}
1930

2031
test isNan {
@@ -23,8 +34,9 @@ test isNan {
2334
try expect(isNan(-math.nan(T)));
2435
try expect(isNan(math.snan(T)));
2536
try expect(!isNan(@as(T, 1.0)));
26-
try expect(!isNan(@as(T, math.inf(T))));
37+
try expect(!isNan(math.inf(T)));
2738
}
39+
try expect(!isNan(1.0));
2840
}
2941

3042
test isSignalNan {
@@ -39,9 +51,13 @@ test isSignalNan {
3951
builtin.zig_backend != .stage2_c)
4052
{
4153
try expect(isSignalNan(math.snan(T)));
54+
} else {
55+
// if this fails, the underlying bug is fixed and above work-around can be removed.
56+
try expect(!isSignalNan(math.snan(T)));
4257
}
4358
try expect(!isSignalNan(math.nan(T)));
4459
try expect(!isSignalNan(@as(T, 1.0)));
4560
try expect(!isSignalNan(math.inf(T)));
4661
}
62+
try expect(!isSignalNan(1.0));
4763
}

0 commit comments

Comments
 (0)