1

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?

Tom
  • 100
  • 1
  • 9

1 Answers1

1

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).

Arc
  • 412
  • 2
  • 16
  • So 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) } ``` 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 doesn't fir the `==` anymore. So I need a function that takes an approximation and compares the range like epsilon = +-0.001. @Arc – Tom Jan 08 '22 at 18:07
  • Hi @James, in Stackoverflow its recommended that you edit your question to improve your minimal reproducible example with additional code, not to write it in the comments because comments are unsuited for line breaks (see [here](https://meta.stackexchange.com/questions/19756/how-do-comments-work) and [here](https://meta.stackexchange.com/questions/74784/how-do-i-add-code-while-writing-comments)), specially if you add code that contains `\\\` single line comments, which make it more difficult to read code in comments. Note that you shouldn't need tolerance comparison if you use MPFR properly. – Arc Jan 09 '22 at 01:25
  • Does your reduced code, even if data not stored in vectors, still manages to generate the failure? – Arc Jan 09 '22 at 01:26
  • Sorry for the late reply; I updated the question and also provided a screenshot of the result when executing the exe. @Arc – Tom Jan 10 '22 at 15:29
  • == is overloaded for mpf_class. And from your code you probably misunderstand what cmp does. – Marc Glisse Jan 10 '22 at 21:11
  • Hi @MarcGlisse, thanks for the advice, but I tried with `if (actual_result == expected_result)` and it did not work. I see now the answer is wrong, will study more. – Arc Jan 10 '22 at 22:10
  • I've updated my question again, the answer is good but the exact values don't equal each other after using my real equations, probably due to the numbers not being exactly 1/6. Any further ideas? @ Arc @MarcGlisse – Tom Jan 10 '22 at 22:37
  • @James, no the answer is not good, I'm looking at the binary values, and although `actual` and `expected` print with same decimal values up to 100 digits or more, the inner binary representations are not the same, so `==` does not work, the string attribution is not trivial as I expected. Still checking. – Arc Jan 10 '22 at 22:52
  • The purpose of GMP is to provide arbitrary-precision arithmetic, so in principle we should be able to make it work without resorting to comparison with tolerance. Marc is right - as always - we should use `==` to compare, it works if expected is calculated with `a/b`, the issue is in attribution, using a numeric constant as you did make it be treated as double, which gives the discrepancy you observed, the issue is string attribution as I did also does not work becuase decimal to binary float conversion is not trivial. – Arc Jan 10 '22 at 22:55
  • Decimal to binary float conversion is not trivial, see [here](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Binary-to-decimal_conversion_with_minimal_number_of_digits) and [here](https://ampl.com/REFS/rounding.pdf). – Arc Jan 10 '22 at 23:03
  • Nope. I concede that you have to use tolerance. This is not a question of getting this specific value of 1/6 approximated right so it converts from decimal to binary correctly. It's a question of what your application does, what are exactly the kind of numbers that you are validating. It makes no sense to keep searching for well matched decimal versions of something that GMP already calculates very well in binary. It's a question of you understanding that the validation has to consider decimal to binary conversion is non-trivial. I'll post an answer with tolerance comparison. – Arc Jan 10 '22 at 23:20