#389·doctest

Approx Epsilon Usage & Constructors

Author: phil-zxxCreated Jun 29, 2020Updated Apr 13, 2026
Labelstype/feature-proposalcategory/floats

Hi,

As I often deal with floating point values, I tend to use the Approx class quite often. Now, let's consider the below example. The CHECK will pass even though the true absolute difference is 0.00001 = 1.e-05. If we use Approx(testValue).epsilon(1.e-12) then the test will give CHECK(false).

cpp
const double testValue = 0.98765;
const double trueValue = 0.98766;
CHECK(Approx(testValue) == trueValue);  // this gives CHECK(true)

Usually, I deem values to be correct if they pass a certain absolute threshold:

cpp
abs(lhs - rhs) < epsilon;                                             // (1)

However, doctest implements this formula:

cpp
abs(lhs - rhs) < epsilon * ( scale + max(abs(lhs), abs(rhs)) );       // (2)

which is slightly different.


Questions:

  1. Why does epsilon default to std::numeric_limits<float>::epsilon()*100 = 1.2e-05 as opposed to std::numeric_limits<double>::epsilon()*100 = 2.2e-14. I am asking since all comparisons are based on double values, and not float values. Personally, I find 1.2e-05 to be quite a low threshold.

  2. In catch2 one can write Approx(testValue).margin(1.e-12).epsilon(0) to exactly achieve method (1) above. (a) Is there benefit in adding extra settings to doctest's Approx class? So that one can achieve both method (1) and (2)? (b) In order to avoid these long factory-like expressions (as catch2 has them), one could allow to add them via an additional constructor. E.g. Approx(double value, double epsilon, double margin) would help shorten the expression to Approx(testValue, 1.e-12, 0).

Happy to help write the code for this, but wanted to get some feedback on my questions first.