1

In one of the C++ modules we have, we have an expression evaluation language.

                                \
EVDataElement NAME::eval(   const EvalContext &ec,                          \
                        const bool recursiveFlag,                       \
                        EVEvaluatorTraceFormatter * trace )             \
{                                                                           \
/*  EVTimer timer("(DECLARE_REL_EVAL)","eval","*", "", 1,1, 3);      */     \
    EVDataElement val (                                                     \
        (left->eval(ec, recursiveFlag, trace))                              \
        OP (right->eval(ec, recursiveFlag, trace)) );                       \
    return val;                                                             \
}

DECLARE_REL_EVAL(oLT,<)
DECLARE_REL_EVAL(oLE,<=)
DECLARE_REL_EVAL(oGT,>)
DECLARE_REL_EVAL(oGE,>=)
DECLARE_REL_EVAL(oEQ,==)
DECLARE_REL_EVAL(oNE,!=)

The module allows certain configuration rules to be set.

SO, if there was a rule in the database that said field1 - field2 > param1, it verifies this condition by passing to the expression language above and returns a result.

The problem we are facing now is say param1 = 3, and field1 = 6.15, and field2 = 3.15

It says the result is true. And I think it is because the difference of 6.15 and 3.15 results in 3.00

And when 3.00 is compared with 3, it thinks 3.00 is greater. Is there any way to work around this?

The reason I said we can't use casting is because we never know what datatype might come through for left and right. I hope this question made sense.

roymustang86
  • 8,054
  • 22
  • 70
  • 101
  • 4
    How do you know that the value is 6.15? Are you positive it's not 6.150000000000001? – Mooing Duck May 02 '12 at 18:17
  • 2
    Obligatory reference: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – Robᵩ May 02 '12 at 18:19
  • 2
    For many cases in which you are comparing floats, you ought to at least find the absolute difference and compare to check whether it is less than some small epsilon (e.g. 1e-10) that suits your needs. – void-pointer May 02 '12 at 18:20
  • @MooingDuck: I don't know how it is stored in memory, but that value is being read from the database, and in the database, it is stored 6.15. Am trying to find the code that reads values from database, but I think it reads it as a double. So, even in memory, it should be 6.150000 – roymustang86 May 02 '12 at 18:31

2 Answers2

6

You're going to get the "usual conversions" when dealing with values of different primitive types. I don't think there's any way around this.

If you're comparing ints to doubles you're going to need to come up with whatever rules you want to use when determining if two values are "close enough." You may consider using the std::modf function (in <cmath>) in making the comparison.

Consider:

#include <iostream>
#include <cmath>

int main()
{
    double d = 6.15 - 3.15;
    std::cout << std::boolalpha;
    std::cout << "d == 3.0: " << (d == 3.0) << '\n';
    double i;
    d = std::modf(d, &i);
    std::cout << "i = " << i << ", d = " << d << '\n';
    std::cout << "i == 3.0: " << (i == 3.0) << '\n';
}

Using Visual Studio 2010 on default settings (i.e., NOT using fastmath) I get:

d == 3.0: false
i = 3, d = 4.44089e-016
i == 3.0: true

3.0 may be representable exactly in binary floating point math, but 6.15 - 3.15 isn't 3.0 in binary floating point math.


There have already been two references to the paper "What Every Computer Scientist Should Know About Floating-Point Arithmetic" which describes how binary floating point math works, and how it doesn't always fit with human expectations. The main point to remember is that you hardly ever want to compare two floating point numbers for equality, especially if one (or both) of those numbers is the result of a math operation.

In your case, though, you're trying to compare a double with an int, and I have to assume you want some rounding. You might want to consider 3.1 to be equivalent to 3. You might not. I really have no idea.

If you were to use the rounding conventions taught in elementary school (round up on .5 or higher), you might do something like:

#include <iostream>
#include <cmath>

int main()
{
    double d = 6.15 - 3.15;
    std::cout << std::boolalpha;
    std::cout << "d == 3.0: " << (d == 3.0) << '\n';
    // note:  this rounds negative numbers the wrong direction
    std::cout << "d is 'close enough' to 3.0: " << (std::floor(d + 0.5) == 3.0) << '\n';
}

There are much more complex possibilities, including one described in the paper.

Max Lybbert
  • 19,717
  • 4
  • 46
  • 69
  • I tried this, and used `double d = 6.1 - 3.1` and I got `i = 2 and d = 1` . What does that suggest? it failed the equality with 3.0, infact it said it is less than 3.0 – roymustang86 May 02 '12 at 18:54
  • @rorymustang: if `std::modf(6.1 - 3.1, &i)` returns `1` (i.e., `d = 1`, then I suspect that `d` is less than, but very close to `1.0`. Especially since `d + 2 < 3.0`. – Max Lybbert May 02 '12 at 19:08
  • 2
    @rorymustang: You may not realize that floating point numbers are often rounded when displayed (this is a separate issue from "floating point math in the computer is calculated differently from what you do by hand"). `std::cout ,, 1.23456789` will probably display something like `1.23457`. – Max Lybbert May 02 '12 at 19:36
  • yes, I used setprecision() and found out how to display all the numbers. – roymustang86 May 02 '12 at 19:38
  • Oops. that sould have been `std::cout << 1.23456789`; or, more to the point, `std::cout << .9999999` will probably display `1`. – Max Lybbert May 02 '12 at 20:13
1

If you want field1 - field2 > param1 for param1 = 3, field1 = 6.15 and field2 = 3.15 then you will need to use infinite precision arithmetic.

Please read the paper on Floating-Point Arithmetic mentioned above.

Miserable Variable
  • 28,432
  • 15
  • 72
  • 133
  • Hey, I am trying to find a way to use infinite precision in C++. How do I display the complete precision? I tried using setprecision http://www.cplusplus.com/reference/iostream/manipulators/setprecision/, but the compiler under solaris doesn't seem to recognize the function. – roymustang86 May 02 '12 at 19:07
  • 1
    @rorymustang86: the C++ Standard Library doesn't offer an infinite precision library. GNU offers GNU GMP ( http://gmplib.org/ ) and GNU MPFR ( http://www.mpfr.org/ ), both under the LGPL. You can find a list of other libraries at http://www.oonumerics.org/oon/ (look for "Multiprecision, arbitrary precision data types"). – Max Lybbert May 02 '12 at 20:19