authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2021-03-13 13:08:46+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-14 17:23:47-04:00
log27d07c6c4de36af1186392d4bec321825403860e
tree39a45001ab3c67640e112fed1a61342ff54101f7
parentd33f0d3375c621db479fbf565855c7d2a7eea735

std: Replace testing fns for floating-point values

Beside handling NaNs and other non-numeric values better we finally offer the same pair of testing predicates in math and testing.

3 files changed, 47 insertions(+), 41 deletions(-)

lib/std/os/linux/io_uring.zig+1-1
...@@ -1353,7 +1353,7 @@ test "timeout (after a relative time)" {...@@ -1353,7 +1353,7 @@ test "timeout (after a relative time)" {
1353 .res = -linux.ETIME,1353 .res = -linux.ETIME,
1354 .flags = 0,1354 .flags = 0,
1355 }, cqe);1355 }, cqe);
1356 testing.expectWithinMargin(@intToFloat(f64, ms), @intToFloat(f64, stopped - started), margin);1356 testing.expectApproxEqAbs(@intToFloat(f64, ms), @intToFloat(f64, stopped - started), margin);
1357}1357}
13581358
1359test "timeout (after a number of completions)" {1359test "timeout (after a number of completions)" {
lib/std/testing.zig+38-36
...@@ -200,67 +200,69 @@ pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anyt...@@ -200,67 +200,69 @@ pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anyt
200 return error.TestFailed;200 return error.TestFailed;
201}201}
202202
203/// This function is intended to be used only in tests. When the actual value is not203pub const expectWithinMargin = @compileError("expectWithinMargin is deprecated, use expectApproxEqAbs or expectApproxEqRel");
204/// within the margin of the expected value,204pub const expectWithinEpsilon = @compileError("expectWithinEpsilon is deprecated, use expectApproxEqAbs or expectApproxEqRel");
205/// prints diagnostics to stderr to show exactly how they are not equal, then aborts.205
206/// This function is intended to be used only in tests. When the actual value is
207/// not approximately equal to the expected value, prints diagnostics to stderr
208/// to show exactly how they are not equal, then aborts.
209/// See `math.approxEqAbs` for more informations on the tolerance parameter.
206/// The types must be floating point210/// The types must be floating point
207pub fn expectWithinMargin(expected: anytype, actual: @TypeOf(expected), margin: @TypeOf(expected)) void {211pub fn expectApproxEqAbs(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) void {
208 std.debug.assert(margin >= 0.0);212 const T = @TypeOf(expected);
213
214 switch (@typeInfo(T)) {
215 .Float => if (!math.approxEqAbs(T, expected, actual, tolerance))
216 std.debug.panic("actual {}, not within absolute tolerance {} of expected {}", .{ actual, tolerance, expected }),
217
218 .ComptimeFloat => @compileError("Cannot approximately compare two comptime_float values"),
209219
210 switch (@typeInfo(@TypeOf(actual))) {
211 .Float,
212 .ComptimeFloat,
213 => {
214 if (@fabs(expected - actual) > margin) {
215 std.debug.panic("actual {}, not within margin {} of expected {}", .{ actual, margin, expected });
216 }
217 },
218 else => @compileError("Unable to compare non floating point values"),220 else => @compileError("Unable to compare non floating point values"),
219 }221 }
220}222}
221223
222test "expectWithinMargin" {224test "expectApproxEqAbs" {
223 inline for ([_]type{ f16, f32, f64, f128 }) |T| {225 inline for ([_]type{ f16, f32, f64, f128 }) |T| {
224 const pos_x: T = 12.0;226 const pos_x: T = 12.0;
225 const pos_y: T = 12.06;227 const pos_y: T = 12.06;
226 const neg_x: T = -12.0;228 const neg_x: T = -12.0;
227 const neg_y: T = -12.06;229 const neg_y: T = -12.06;
228230
229 expectWithinMargin(pos_x, pos_y, 0.1);231 expectApproxEqAbs(pos_x, pos_y, 0.1);
230 expectWithinMargin(neg_x, neg_y, 0.1);232 expectApproxEqAbs(neg_x, neg_y, 0.1);
231 }233 }
232}234}
233235
234/// This function is intended to be used only in tests. When the actual value is not236/// This function is intended to be used only in tests. When the actual value is
235/// within the epsilon of the expected value,237/// not approximately equal to the expected value, prints diagnostics to stderr
236/// prints diagnostics to stderr to show exactly how they are not equal, then aborts.238/// to show exactly how they are not equal, then aborts.
239/// See `math.approxEqRel` for more informations on the tolerance parameter.
237/// The types must be floating point240/// The types must be floating point
238pub fn expectWithinEpsilon(expected: anytype, actual: @TypeOf(expected), epsilon: @TypeOf(expected)) void {241pub fn expectApproxEqRel(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) void {
239 std.debug.assert(epsilon >= 0.0 and epsilon <= 1.0);242 const T = @TypeOf(expected);
243
244 switch (@typeInfo(T)) {
245 .Float => if (!math.approxEqRel(T, expected, actual, tolerance))
246 std.debug.panic("actual {}, not within relative tolerance {} of expected {}", .{ actual, tolerance, expected }),
247
248 .ComptimeFloat => @compileError("Cannot approximately compare two comptime_float values"),
240249
241 // Relative epsilon test.
242 const margin = math.max(math.fabs(expected), math.fabs(actual)) * epsilon;
243 switch (@typeInfo(@TypeOf(actual))) {
244 .Float,
245 .ComptimeFloat,
246 => {
247 if (@fabs(expected - actual) > margin) {
248 std.debug.panic("actual {}, not within epsilon {}, of expected {}", .{ actual, epsilon, expected });
249 }
250 },
251 else => @compileError("Unable to compare non floating point values"),250 else => @compileError("Unable to compare non floating point values"),
252 }251 }
253}252}
254253
255test "expectWithinEpsilon" {254test "expectApproxEqRel" {
256 inline for ([_]type{ f16, f32, f64, f128 }) |T| {255 inline for ([_]type{ f16, f32, f64, f128 }) |T| {
256 const eps_value = comptime math.epsilon(T);
257 const sqrt_eps_value = comptime math.sqrt(eps_value);
258
257 const pos_x: T = 12.0;259 const pos_x: T = 12.0;
258 const pos_y: T = 13.2;260 const pos_y: T = pos_x + 2 * eps_value;
259 const neg_x: T = -12.0;261 const neg_x: T = -12.0;
260 const neg_y: T = -13.2;262 const neg_y: T = neg_x - 2 * eps_value;
261263
262 expectWithinEpsilon(pos_x, pos_y, 0.1);264 expectApproxEqRel(pos_x, pos_y, sqrt_eps_value);
263 expectWithinEpsilon(neg_x, neg_y, 0.1);265 expectApproxEqRel(neg_x, neg_y, sqrt_eps_value);
264 }266 }
265}267}
266268
test/stage1/behavior/vector.zig+8-4
...@@ -4,7 +4,7 @@ const mem = std.mem;...@@ -4,7 +4,7 @@ const mem = std.mem;
4const math = std.math;4const math = std.math;
5const expect = std.testing.expect;5const expect = std.testing.expect;
6const expectEqual = std.testing.expectEqual;6const expectEqual = std.testing.expectEqual;
7const expectWithinEpsilon = std.testing.expectWithinEpsilon;7const expectApproxEqRel = std.testing.expectApproxEqRel;
8const Vector = std.meta.Vector;8const Vector = std.meta.Vector;
99
10test "implicit cast vector to array - bool" {10test "implicit cast vector to array - bool" {
...@@ -514,10 +514,14 @@ test "vector reduce operation" {...@@ -514,10 +514,14 @@ test "vector reduce operation" {
514 switch (@typeInfo(TX)) {514 switch (@typeInfo(TX)) {
515 .Int, .Bool => expectEqual(expected, r),515 .Int, .Bool => expectEqual(expected, r),
516 .Float => {516 .Float => {
517 if (math.isNan(expected) != math.isNan(r)) {517 const expected_nan = math.isNan(expected);
518 std.debug.panic("unexpected NaN value!\n", .{});518 const got_nan = math.isNan(r);
519
520 if (expected_nan and got_nan) {
521 // Do this check explicitly as two NaN values are never
522 // equal.
519 } else {523 } else {
520 expectWithinEpsilon(expected, r, 0.001);524 expectApproxEqRel(expected, r, math.sqrt(math.epsilon(TX)));
521 }525 }
522 },526 },
523 else => unreachable,527 else => unreachable,