16

I learnt that comparing a double using == is not a wise practice. However I was wondering if checking if a double has been initialized could be dangerous or not. For example, knowing that a variable doubleVar cannot be zero if it has been initialized, is it safe to do this?

Foo::Foo(){
    doubleVar = 0.0;  // of type double
}

void Foo::Bar(){
    if(doubleVar == 0){ // has been initialized?
        //...
    }else{
        //...
    }
}
ouah
  • 142,963
  • 15
  • 272
  • 331
HAL9000
  • 3,562
  • 3
  • 25
  • 47
  • 4
    That a) isn't initializing it to 0, it's assigning 0 to it, and b) comparing it with an `int` 0, not a `double` 0.0 like it was assigned. – chris Jan 28 '14 at 20:25
  • Yes. For this case it is safe. – haccks Jan 28 '14 at 20:25
  • I'm confused. Why can't a `doubleVar` be zero if it has been initialized? Can't you initialize it as zero? Who told you it isn't a wise practice to compare a double with == ? What was the reason they gave? – crush Jan 28 '14 at 20:25
  • If you're after checking whether it's been initialized, go for something like `boost::optional`. It works the same for every type and doesn't use up a possible value. – chris Jan 28 '14 at 20:27
  • @crush it's an hypotesis. In my problem doubleVar cannot be 0 (for example a denominator). – HAL9000 Jan 28 '14 at 20:28
  • 4
    @crush _'Who told you it isn't a wise practice to compare a double with =='_ IMHO it's [common agreement](http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison)! – πάντα ῥεῖ Jan 28 '14 at 20:29
  • @crush 'Who told you it isn't a wise practice to compare a double with ==' if you tried you can answer yourself ;-) You'll almost never reach exactly the same value when doing, for example, physical or mathematical computation, then it would always return false, even if you are very near to your "target". – HAL9000 Jan 28 '14 at 20:31
  • @HAL9000 Checking the denominator is an acceptable use-case. Any non-0 (but close to 0 value) is different from 0. E.g. division may result in InF, but not NaN - at least in IEEE754. – user2864740 Jan 28 '14 at 20:33
  • 2
    @chris being that the constructor, that assignment is the initialization of the variable. And 0 == 0.0 is true, so, for the transitive property of equality relation, it's indifferent to write 0 or 0.0 in this case. – HAL9000 Jan 28 '14 at 20:53
  • 1
    @HAL9000, It would only be initialized if it was in the constructor initializer list, unless you were using C++11 in-class member initialization. It isn't like Java. – chris Jan 28 '14 at 21:02

1 Answers1

20

In IEEE-754, long story short:

double d;

d = 0.0;
d == 0.0   // guaranteed to evaluate to true, 0.0 can be represented exactly in double

but

double d;

d = 0.1;
d == 0.1   // not guaranteed to evaluate to true
ouah
  • 142,963
  • 15
  • 272
  • 331
  • 6
    Why is the latter *not* guaranteed to be true? It is the *same* value (which should go through the same promotions, as required). – user2864740 Jan 28 '14 at 20:30
  • 1
    @user2864740 `0.1` cannot be represented exactly in `double` (IEEE-754 binary64 type) and C says: *(C99, 5.2.4.2.2p8) "Except for assignment and cast (which remove all extra range and precision), the values of operations with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type."* – ouah Jan 28 '14 at 20:33
  • 4
    @HAL9000 I am arguing that *doesn't matter* because the same value is used in *both places* and thus has the *same* bit pattern internally. This is opposed to something like `0.3 + 0.2 == 0.5` which involves math. – user2864740 Jan 28 '14 at 20:34
  • @HAL9000 So you're telling me that if I were to compare 0.75 to 0.75, I would get false? – crush Jan 28 '14 at 20:39
  • Your only guarantees is that negative-zero and positive-zero will be equal in an equality check (`==`) and that comparing to zero will succeed if you explicitly set it to zero just prior to the comparison operation. You should never be doing equality checks with type float/double, and should instead be using tolerances/error-bounds, and inequality comparisons (`<`,`<=`,`>`,`>=`). – Cloud Jan 28 '14 at 20:39
  • 2
    @HAL9000: If you set a double to 0.75 and it doesn’t have exactly that value, something is very, very wrong. 0.75 is exactly representable in every floating-point arithmetic system used in production. Perhaps you mean a value that isn’t typically exactly representable like 0.6? – Stephen Canon Jan 28 '14 at 20:42
  • 3
    @ouah: Strictly speaking, in C both cases *should* evaluate to true (6.4.4.2 p4 "An unsuffixed floating constant has type double.” and p7 "The translation-time conversion of floating constants should match the execution-time conversion of character strings by library functions, such as strtod, given matching inputs suitable for both conversions, the same result format, and default execution-time rounding.”) However, skanky compilers definitely exist that are a bit fast-and-loose with these issues. – Stephen Canon Jan 28 '14 at 20:46
  • @Stephen Canon Cannot the `0.1` of `d == 0.1` get evaluated to higher precision than `double` and then fail the `d == 0.1`? Hmm - maybe that is your preceding point. – chux - Reinstate Monica Jan 28 '14 at 20:51
  • @StephenCanon good point, even if a *recommend practice* should not be taken as a normative rule. But the 64) footnote to this quote also says *The specification for the library functions recommends more accurate conversion than required for floating constants*. – ouah Jan 28 '14 at 20:52
  • Maybe I am missing something; a they are both double values (either from "being in" a variable or from a double literal), so any "higher precision" should apply to both, no? – user2864740 Jan 28 '14 at 21:11
  • 2
    @user2864740: What can happen is that `0.1` is converted from source text to an internal format with more precision than `double`. Then, in `d = 0.1`, this internal value is converted to `double`, because assignment requires using the nominal type of the assigned object; excess precision must be discarded. This conversion can produce a different value. Then, in `d == 0.1`, the `double` stored in `d`, with its changed value, is compared to the `0.1` with excess precision. The values differ, so the comparison is false. – Eric Postpischil Jan 28 '14 at 21:18
  • 2
    This answer assumes IEEE 754 in order to claim that the assignment and comparison with zero work, but then claims that assignment and comparison with .1 might not work. If the same assumption is made for both, that the C implementation is bound to IEEE 754 in a reasonable way, then both work. If the assumption is not made, then neither is guaranteed by the C standard. (The C standard does not guarantee that the source text `0.0` is converted to exactly zero. It may be converted to an immediately adjacent value, e.g., the minimum positive `long double` value.) – Eric Postpischil Jan 28 '14 at 21:18
  • @EricPostpischil I assume IEEE 754 in the answer, because in IEEE 754 `0.0` is represented exactly and `0.1` is not represented exactly. – ouah Jan 28 '14 at 21:24
  • C++ requires that a floating-point literal be converted to the exact value if it is representable, so this cannot happen with `0.0` in C++ (barring some bizarre floating-point format that cannot represent zero, which might be prohibited by other parts of the C++ standard). – Eric Postpischil Jan 28 '14 at 21:24
  • 1
    @ouah: The problem is not assuming IEEE 754; the problem is assuming IEEE 754 in one part of the answer and not in another. Assuming IEEE 754 by itself is actually meaningless, because IEEE 754 just says that floating-point formats and operations exist and behave in certain ways; it does not say that `a + b` in C is the IEEE 754 addition operation, for example. You have to bind IEEE 754 to C in some way. If you assume IEEE 754 conversions from source text decimal to internal binary `double`, then both examples always work. If you do not, then C permits `0.0` to be converted as I stated. – Eric Postpischil Jan 28 '14 at 21:26
  • @EricPostpischil I was actually assuming IEEE 754 in both parts of the answer; I didn't think the C latitude to use larger precision (e.g., binary128) types in an expression involving binary64 operand was constraint by IEEE 754 and contradictory to IEEE 754. If this is the case let's say I actually assume in the answer IEEE 754 formats for floating-point types :) – ouah Jan 28 '14 at 21:35
  • 1
    @ouah: As I said, you need to have a binding from IEEE 754. There are three relevant choices: When source text decimal is converted to internal binary, (0) it is an IEEE-754 decimal-to-binary conversion with a target format of some extended precision, (1) it is an IEEE-754 decimal-to-binary conversion with a target format of double precision, or (2) it is not an IEEE-754 conversion. (0) is a weird binding because allowing extended precision when the user does not request it is contrary to the intent of IEEE 754, and it may be different in different implementations, so it is not really “bound”. – Eric Postpischil Jan 28 '14 at 21:57
  • 1
    In choice (1), both `0.0` and `0.1` produce true, because there is no excess precision to interfere. In choice (2), neither the `0.0` nor the `0.1` case is guaranteed to return true; each of them may be converted to an adjacent `long double` value, then changed by conversion to `double`, then compare unequal. – Eric Postpischil Jan 28 '14 at 21:59
  • 7
    Nobody is doing the real world program!! My GCC compiler gives FALSE as the return of the following code: `double x = 0.1; return x == 0.1;`. This is right, because the constant `FLT_EVAL_METHOD` is 2 in my case, meaning that every constant and intermediate computation is done in `long double` precision. The exceptions are **casts and assignments**. So, the `double` variable `x`, which was initialized to `0.1`, has the `double` precision value `0.1`, but the constant itself `0.1` always is a `long double`, so the comparisson has to be FALSE, which indeed happens. – pablo1977 Jan 28 '14 at 22:38