Search code examples
gmpapproximationcatch2

Approximation using gmp mpf_class


I am writing a UnitTest using Catch2.
I want to check if two vectors are equal. They look like the following using gmplib:

std::vector<mpf_class> result

Due to me 'faking' the expected_result vector, I get the following message after a failed test:

unittests/test.cpp:01: FAILED:
REQUIRE( actual_result == expected_result )
with expansion:
  { 0.5, 0.166667, 0.166667, 0.166667 }
   ==
  { 0.5, 0.166667, 0.166667, 0.166667 }

So I was looking for a function that could do an approximation for me.
I just wasn't successful in finding a solution that worked out for me.

I found some Comparison Functions but they do not work on my project.

EDIT: The "minimal, reproducible example would simply be:

TEST_CASE("DemoTest") { 
// simplified: 
mpf_class a = 1; 
mpf_class b = 6; 

mpf_class actual_result = a / b; 
mpf_class expected_result= 0.16666666667; 

REQUIRE(actual_result == expected_result);
} 

This is the result of the mentioned example.

The "only" difference to my real application is that the results are stored in vectors. But because I am only "faking" the result by saying it is "0.1666666667" it probably doesn't fit the == anymore. So I need a function that takes an approximation and compares the range like epsilon = +-0.001.

Edit: After implementing the solution @Arc suggested it worked well until I had some Values that were not complete "even".
So I have a failure with the following values:

actual   0.16666666666666666666700000000000000000000000000000
expected 0.16666666666666665741500000000000000000000000000000

Even though my "expected" value looks like this:

mpf_class expected = 0.16666666666666666666700000000000000000000000000000

Getting back to my original question if there is a way I can compare an approximation of the number with an epsilon of like +-0.0001 or what would be the best way to fix this issue?


Solution

  • First, we need to see some Minimal, Reproducible Example to be sure of what is happening. You can for example cut down some code from your test.cpp until you are left with just a few lines of code, but the issue still happens. Also, please provide compilation and running instructions. Frequently, a little bit of explanation on what your goals are may also help. As Catch2 is available on GitHub you don't need to provide it.

    Without seeing the code, the best I can guess is that your code is trying to comparing mpf_t types in the mpf_class using the == operator, which I'm afraid has not been overload (see here). You should compare mpf_ts with the cmp function, since the C type mpf_t is actually an struct containing the pointer to the actual significand limbs. Check some usage examples in the tests/cxx/ directory of GMP (like here).

    I note you are using GNU MP 4.1 version which is very old, you probably want to move to the 6.2.1 latest version if possible. Also, for using floats it's recommended that you use the GNU MPFR library instead of GMP floats.

    EDIT: I did not yet manage to run Catch2, but the issue with your code is the expected_result is actually not equal to the actual_result. In GMP mpf_t variables are created with a 64-bit significand precision (on 64-bit machines), so that the division a / b actually results in a binary that prints 0.166666666666666666667 (that's 19 sixes after the digit 1). Try printing the result with gmp_printf("%.50Ff\n", actual_result);, because the standard cout output will only give you the value rounded to 6 digits: 0.166667.

    But the problem is you can't just assign this like expected_result = 0.166666666666666666667 because in C/C++ numeric constants are parsed as double, thus you have to use the string overload attribution to get more precision.

    But you can't also manage to easily (or, in general, justifiably) coin a decimal string that will correctly convert to the exact same binary given by a / b because decimal to float conversion has subtleties, see for example here and here.

    So, it all depends on your application and the kind of numerical validation you aim to do. If you know that your decimal validation values are correct to some known precision, and if you set the mpf_t variables to withstanding precision (using for example mpf_set_prec), then you can use tolerance comparison, like so.

    in C++ (without Catch2), it works like this:

    #include <iostream>
    #include <gmpxx.h>
    using namespace std;
    
    int main (void) 
    {
        mpf_class a = 1;
        mpf_class b = 6;
        mpf_class actual = a / b;
        mpf_class expected;
        mpf_class tol;
        expected = "0.166666666666666666666666666666667";
        tol = "1e-30";
        cout << "actual " << actual << "\n";
        cout << "expected " << expected << "\n";
        gmp_printf("actual %.50Ff\n", actual);
        gmp_printf("expected %.50Ff\n", expected);
        gmp_printf("tol %.50Ff\n", tol);
        mpf_class diff = expected - actual;
        gmp_printf("diff %.50Ff\n", diff);
        if (abs(actual - expected) < tol)
            cout << "ok\n";
        else
            cout << "nop\n";
        return 0;
    }
    

    And compile with -lgmpxx -lgmp options.

    It produces the output:

    actual 0.166667
    expected 0.166667
    actual 0.16666666666666666666700000000000000000000000000000
    expected 0.16666666666666666666700000000000000000000000000000
    tol 0.00000000000000000000000000000100000000000000000000
    diff 0.00000000000000000000000000000000033333529249058470
    ok
    

    If I understand Catch2 well, it should be ok if you assign expected_result with string then compare with REQUIRE(abs(actual - expected) < tol).