1

I am familiar with the inaccuracies of adding/subtracting floats together, but I'm wondering if adding any float to a float value of zero would result in similar inaccuracies as I'm not completely familiar with how floats are added together in such a scenario.

To put my question simply:

float sum = 0.0f;
sum += value;

assert(sum == value); // Is this guaranteed?

(EDIT: As pointed out in the comments, a similar question exists where if adding zero to a float will produce a change to the variable. I could understand how a CPU would be optimized to ignore the addition if the operand is zero, however I'm wondering if the opposite is guaranteed to be true where if adding a non-zero float operand to a variable that's storing a zero is possible to result in a value unequal to the non-zero value being added.)

For clarification why this question was stemmed in my mind, the actual code I'm working on involves clearing a float to zero under certain conditions and then incrementing the float by a certain value outside of the conditional block, but I thought it might be better to instead set the float to the value instead and skip incrementing it.

For example:

float sum = 0.0f;
for (auto && thing : things)
{
    if (thing.someCondition)
        sum = 0.0f;
    else if (thing.otherCondition)
        { /* more code here... */ }
    else
        { /* more code here... */ }

    sum += thing.value;
}

Compared to:

float sum = 0.0f;
for (auto && thing : things)
{
    if (thing.someCondition)
    {
        sum = thing.value;
        continue; // Continue to skip incrementing.
    }
    else if (thing.otherCondition)
    {
        /* more code here... */
    }
    else
    {
        /* more code here... */
    }

    sum += thing.value;
}

(Ignore the fact that the last block of code could have the increment logic moved into the other cases and avoid using continue. I just kept it formatted this way to contrast the previous block where incrementing should always performed.)

Chris
  • 325
  • 1
  • 3
  • 11
  • 1
    You don't have to set it to `0.0` first. The direct assignment will accomplish the same thing without wasted operations. There is no functional or accuracy difference between `sum = thing.value;` and `sum = 0.0f; sum = thing.value;`, and the latter simply wastes FPU ops. The end result is the same; there may be an issue with the exact representation of `thing.value`, but it will be the same regardless of whether you make the `0.0` assignment first or not. – Ken White Jul 21 '20 at 01:33
  • It's not a question of if I should set it to zero beforehand but if the result is guaranteed to be the same. See the remaining part of the question. – Chris Jul 21 '20 at 01:37
  • 6
    `0` is one thing that can be represented exactly in IEEE-754 floating point format. So adding a floating point value to zero will not change its value. See also: [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) and [Why Are Floating Point Numbers Inaccurate?](https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate) and [Floating point comparison `a != 0.7`](https://stackoverflow.com/questions/6883306/floating-point-comparison-a-0-7) – David C. Rankin Jul 21 '20 at 01:38
  • You apparently didn't read my comment, where I addressed that specifically. You should read it again. – Ken White Jul 21 '20 at 01:41
  • @KenWhite Probably because you edited it after I replied, lol – Chris Jul 21 '20 at 02:04
  • See [Adding 0 to a Float/Double Type in C++](https://stackoverflow.com/questions/38432323/adding-0-to-a-float-double-type-in-c). Short answer: it is guaranteed that they compare equal if the implementation is IEEE-754 compliant. – dxiv Jul 21 '20 at 02:11
  • @Chris: Um, no. That's not possible. The edit was made before your comment. Check the timestamps. My edit was made within seconds of my original comment; your reply came nearly 5 minutes later. – Ken White Jul 21 '20 at 02:11
  • @KenWhite I can't see the timestamps of edits, but if that's the case it must've been cached on my end or something because it was a lot shorter when I replied. – Chris Jul 21 '20 at 02:26
  • 1
    You might want to narrow down the question by specifying the type of `thing.value`. If it is double, and holds a value that a `float` cannot, then `sum = thing.value + 0.0f` will promote `0.0f` to `double`, do the addition, and then convert the result of addition to `float`. The promotion and addition in that case don't change value, but precision may be lost in converting back to store the result in `sum`. – Peter Jul 21 '20 at 02:30
  • @Peter: Round tripping a `float` to `double` and back doesn't change the value, at least on any sane C++ implementation. (Where `double`'s exponent and mantissa are both at least as wide as `float`'s, and both are binary, and the exponent range is centered around 1.0, not for example only going down to 2^-10 but going up to 2^+244.) And `x += 0.0` doesn't change `x` for `double x`, except for `-0.0` becoming `+0.0`. So there's no way for precision to be lost in the conversion back to float; it's exact because the double held a value exactly representable as a float. – Peter Cordes Jun 18 '22 at 09:30
  • @PeterCordes Yeah, it is true that value of a `float` can survive a round-trip to `double` and back, and I wasn't suggesting otherwise. But initialising a `float` with a `double` and subsequently adding a (non-zero) `double` to a `float` repeatedly can be affected by loss of precision. – Peter Jun 19 '22 at 04:41
  • @Peter: Oh right, I forgot the details of this question; I'd been looking for duplicates for a question about adding a `+0` in an expression, not as the initializer for a variable. Yeah, you're right, this question has two separate variables, one of unknown type that might be more precise than what we're summing into. (Canonical Q&A: [Why does Clang optimize away x \* 1.0 but NOT x + 0.0?](https://stackoverflow.com/q/33272994)) – Peter Cordes Jun 19 '22 at 04:48

1 Answers1

2

Adding zero to another float produces a value that compares equal to the other float (or the other float is nan and the result is nan and so they don’t compare equal but are both nan).

But it's not a no-op, unlike subtracting zero or adding negative zero. -0. + 0. produces +0., which compares equal to -0. but is a distinct value if you use copysign, examine the bit-pattern, or other such things.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Ben
  • 9,184
  • 1
  • 43
  • 56
  • what about (0.0) + (-0.0)? – Elliott Jul 21 '20 at 02:06
  • 1
    Those are equal. ;-) – Ben Jul 21 '20 at 02:14
  • 1
    Well, just checking with gcc it seems to always revert to the positive in this case. – Elliott Jul 21 '20 at 02:20
  • 2
    @Elliott [That depends on the rounding mode. Adding negative zero is the "real" identity under the usual default rounding mode.](https://stackoverflow.com/q/48255293/5684257) – HTNW Jul 21 '20 at 02:23
  • Also, they're not equivalent. What if you have a function that takes in a number and does different actions depending on if it's positive or negative? Maybe you could say that the function is badly designed, but it means the difference in two numbers can have real-world effect. Better that your program recognises that and deals with it properly than hope that all other software that your number may interact with will handle your number properly. – Elliott Jul 21 '20 at 02:23
  • @Elliott They are not the same, but they compare equal `0.0 == -0.0`, by both [C++ rules](https://en.cppreference.com/w/cpp/language/types): "*special values - the negative zero, -0.0. It compares equal to the positive zero, but ...*" and [IEEE-754 rules](https://en.wikipedia.org/wiki/Signed_zero#Comparisons). – dxiv Jul 21 '20 at 02:29
  • While I can understand that adding zero to a variable wouldn't produce a change, I can't help but feel the opposite might not be also true (because of how weird floats can sometimes be) where adding a non-zero value to a variable that is holding zero will not be accurate. Do you know if that is also the case and if there are any resources that clarify/support this? – Chris Jul 21 '20 at 02:50
  • 1
    @Chris Floats are not that weird. `x + y` is defined to add `x` and `y` with "infinite" precision and then round as needed. Mathematical `+` is commutative => float `+` is commutative. – HTNW Jul 21 '20 at 03:33
  • @HTNW If 20 years of programming has taught me anything it's to not be so sure of myself nor assume the most simple of processes in ever-changing technology. – Chris Jul 21 '20 at 03:39
  • 3
    @HTNW It is incorrect to assume that floating point operations have a property just because the math they represent has that property. Counter example : multiplication is associative but not with floating point arithmetics. – François Andrieux Jul 21 '20 at 04:37
  • 1
    @FrançoisAndrieux Correct. Using dots to denote floating point operations and making rounding explicit: `x .+. y = round(x + y) = round(y + x) = y .+. x`. `.+.` is commutative, following directly from commutativity of actual addition. `x .*. (y .*. z) = round(x * round(y * z)) ?!= round(round(x * y) * z) = (x .*. y) .*. z`. `.*.` is not always associative. I elided the proof in my original comment; but it is good to be careful. – HTNW Jul 21 '20 at 07:07