aboutsummaryrefslogtreecommitdiffstats
path: root/docs/comparing-floating-point-numbers.md
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-29 19:25:29 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-29 19:25:29 +0100
commitbc524d70253a4ab2fe40c3ca3e5666e267c0a4d1 (patch)
tree1e629e7b46b1d9972a973bc93fd100bcebd395be /docs/comparing-floating-point-numbers.md
downloadnihil-vendor/catch2/3.8.1.tar.gz
nihil-vendor/catch2/3.8.1.tar.bz2
Diffstat (limited to 'docs/comparing-floating-point-numbers.md')
-rw-r--r--docs/comparing-floating-point-numbers.md192
1 files changed, 192 insertions, 0 deletions
diff --git a/docs/comparing-floating-point-numbers.md b/docs/comparing-floating-point-numbers.md
new file mode 100644
index 0000000..ab5ba6d
--- /dev/null
+++ b/docs/comparing-floating-point-numbers.md
@@ -0,0 +1,192 @@
+<a id="top"></a>
+# Comparing floating point numbers with Catch2
+
+If you are not deeply familiar with them, floating point numbers can be
+unintuitive. This also applies to comparing floating point numbers for
+(in)equality.
+
+This page assumes that you have some understanding of both FP, and the
+meaning of different kinds of comparisons, and only goes over what
+functionality Catch2 provides to help you with comparing floating point
+numbers. If you do not have this understanding, we recommend that you first
+study up on floating point numbers and their comparisons, e.g. by [reading
+this blog post](https://codingnest.com/the-little-things-comparing-floating-point-numbers/).
+
+
+## Floating point matchers
+
+```
+#include <catch2/matchers/catch_matchers_floating_point.hpp>
+```
+
+[Matchers](matchers.md#top) are the preferred way of comparing floating
+point numbers in Catch2. We provide 3 of them:
+
+* `WithinAbs(double target, double margin)`,
+* `WithinRel(FloatingPoint target, FloatingPoint eps)`, and
+* `WithinULP(FloatingPoint target, uint64_t maxUlpDiff)`.
+
+> `WithinRel` matcher was introduced in Catch2 2.10.0
+
+As with all matchers, you can combine multiple floating point matchers
+in a single assertion. For example, to check that some computation matches
+a known good value within 0.1% or is close enough (no different to 5
+decimal places) to zero, we would write this assertion:
+
+```cpp
+ REQUIRE_THAT( computation(input),
+ Catch::Matchers::WithinRel(expected, 0.001)
+ || Catch::Matchers::WithinAbs(0, 0.000001) );
+```
+
+
+### WithinAbs
+
+`WithinAbs` creates a matcher that accepts floating point numbers whose
+difference with `target` is less-or-equal to the `margin`. Since `float`
+can be converted to `double` without losing precision, only `double`
+overload exists.
+
+```cpp
+REQUIRE_THAT(1.0, WithinAbs(1.2, 0.2));
+REQUIRE_THAT(0.f, !WithinAbs(1.0, 0.5));
+// Notice that infinity == infinity for WithinAbs
+REQUIRE_THAT(INFINITY, WithinAbs(INFINITY, 0));
+```
+
+
+### WithinRel
+
+`WithinRel` creates a matcher that accepts floating point numbers that
+are _approximately equal_ to the `target` with a tolerance of `eps.`
+Specifically, it matches if
+`|arg - target| <= eps * max(|arg|, |target|)` holds. If you do not
+specify `eps`, `std::numeric_limits<FloatingPoint>::epsilon * 100`
+is used as the default.
+
+```cpp
+// Notice that WithinRel comparison is symmetric, unlike Approx's.
+REQUIRE_THAT(1.0, WithinRel(1.1, 0.1));
+REQUIRE_THAT(1.1, WithinRel(1.0, 0.1));
+// Notice that inifnity == infinity for WithinRel
+REQUIRE_THAT(INFINITY, WithinRel(INFINITY));
+```
+
+
+### WithinULP
+
+`WithinULP` creates a matcher that accepts floating point numbers that
+are no more than `maxUlpDiff`
+[ULPs](https://en.wikipedia.org/wiki/Unit_in_the_last_place)
+away from the `target` value. The short version of what this means
+is that there is no more than `maxUlpDiff - 1` representable floating
+point numbers between the argument for matching and the `target` value.
+
+When using the ULP matcher in Catch2, it is important to keep in mind
+that Catch2 interprets ULP distance slightly differently than
+e.g. `std::nextafter` does.
+
+Catch2's ULP calculation obeys these relations:
+ * `ulpDistance(-x, x) == 2 * ulpDistance(x, 0)`
+ * `ulpDistance(-0, 0) == 0` (due to the above)
+ * `ulpDistance(DBL_MAX, INFINITY) == 1`
+ * `ulpDistancE(NaN, x) == infinity`
+
+
+**Important**: The WithinULP matcher requires the platform to use the
+[IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) representation for
+floating point numbers.
+
+```cpp
+REQUIRE_THAT( -0.f, WithinULP( 0.f, 0 ) );
+```
+
+
+## `Approx`
+
+```
+#include <catch2/catch_approx.hpp>
+```
+
+**We strongly recommend against using `Approx` when writing new code.**
+You should be using floating point matchers instead.
+
+Catch2 provides one more way to handle floating point comparisons. It is
+`Approx`, a special type with overloaded comparison operators, that can
+be used in standard assertions, e.g.
+
+```cpp
+REQUIRE(0.99999 == Catch::Approx(1));
+```
+
+`Approx` supports four comparison operators, `==`, `!=`, `<=`, `>=`, and can
+also be used with strong typedefs over `double`s. It can be used for both
+relative and margin comparisons by using its three customization points.
+Note that the semantics of this is always that of an _or_, so if either
+the relative or absolute margin comparison passes, then the whole comparison
+passes.
+
+The downside to `Approx` is that it has a couple of issues that we cannot
+fix without breaking backwards compatibility. Because Catch2 also provides
+complete set of matchers that implement different floating point comparison
+methods, `Approx` is left as-is, is considered deprecated, and should
+not be used in new code.
+
+The issues are
+ * All internal computation is done in `double`s, leading to slightly
+ different results if the inputs were floats.
+ * `Approx`'s relative margin comparison is not symmetric. This means
+ that `Approx( 10 ).epsilon(0.1) != 11.1` but `Approx( 11.1 ).epsilon(0.1) == 10`.
+ * By default, `Approx` only uses relative margin comparison. This means
+ that `Approx(0) == X` only passes for `X == 0`.
+
+
+### Approx details
+
+If you still want/need to know more about `Approx`, read on.
+
+Catch2 provides a UDL for `Approx`; `_a`. It resides in the `Catch::literals`
+namespace, and can be used like this:
+
+```cpp
+using namespace Catch::literals;
+REQUIRE( performComputation() == 2.1_a );
+```
+
+`Approx` has three customization points for the comparison:
+
+* **epsilon** - epsilon sets the coefficient by which a result
+can differ from `Approx`'s value before it is rejected.
+_Defaults to `std::numeric_limits<float>::epsilon()*100`._
+
+```cpp
+Approx target = Approx(100).epsilon(0.01);
+100.0 == target; // Obviously true
+200.0 == target; // Obviously still false
+100.5 == target; // True, because we set target to allow up to 1% difference
+```
+
+
+* **margin** - margin sets the absolute value by which
+a result can differ from `Approx`'s value before it is rejected.
+_Defaults to `0.0`._
+
+```cpp
+Approx target = Approx(100).margin(5);
+100.0 == target; // Obviously true
+200.0 == target; // Obviously still false
+104.0 == target; // True, because we set target to allow absolute difference of at most 5
+```
+
+* **scale** - scale is used to change the magnitude of `Approx` for the relative check.
+_By default, set to `0.0`._
+
+Scale could be useful if the computation leading to the result worked
+on a different scale than is used by the results. Approx's scale is added
+to Approx's value when computing the allowed relative margin from the
+Approx's value.
+
+
+---
+
+[Home](Readme.md#top)