The answer to the question in the title is a resounding “maybe”. The writer of a piece of code wanted to avoid a division-by-zero error by checking whether the divisor of type `float`

is not equal to 0.0f.

During a code review, I found the following piece of code.

// signal.h struct Signal { int id; int offset; float factor; int value; }; // builder.h #include "signal.h" class Builder { void updateSignalValue(int id, int value); QList<Signal> m_signalColl; }; // builder.cpp void Builder::updateSignalValue(int id, int rawValue) { for (auto& signal : m_signalColl) { if (signal.id == id) { rawValue -= signal.offset;if (signal.factor != 0.0f) { rawValue /= signal.factor; }signal.value = rawValue; } } }

The function `updateSignalValue()`

searches for a `Signal`

with the identifier `id`

in the list `m_signalColl`

of `Signal`

objects. If it finds one, it calculates the new value from the `rawValue`

and stores the new value in the `Signal`

object. The code writer wants to avoid a division-by-zero error with the condition `signal.factor != 0.0f`

.

My first thought was whether this condition can ever be false. It turns out it can. If we simply assign 0.0f to `signal.factor`

, the condition will always be false, because 0.0f is equal to 0.0f. The function `updateSignalValue()`

would behave as follows.

m_signalColl.push_back(Signal{1, 0, 0.0f, 5}); updateSignalValue(1, 7); // m_signalColl[0].value == 7 updateSignalValue(1, 4); // m_signalColl[0].value == 4

This works fine, because 0.0f has a unique representation as a `float`

.

As soon as we calculate the value of `signal.factor`

, we can introduce rounding errors with every arithmetic operation. We use the following function to calculate zero in a non-trivial way.

float Builder::calculateZero(float start, float decrement, int count) { for (int i = 0; i < count; ++i) { start -= decrement; } return start; }

Let us see how the results change when we use this function.

m_signalColl.push_back(Signal{2, 0, calculateZero(1.0f, 0.2f, 5), 5}); // m_signalColl[0].factor = 0.000000029802322 updateSignalValue(2, 7); // m_signalColl[0].value == 234881024 updateSignalValue(2, 4); // m_signalColl[0].value == 134217728

We see that the signal factor is nearly zero (0.000000029802322) but not exactly. The reason is that 0.2f has no exact representation as a binary floating-point number. It comes out as 0.200000002980232 when rounded to 15 digits after the decimal point. When `calculateZero()`

subtracts 0.200000002980232 5 times from 1.0f, the result will be slightly less than 0.0f. The initial rounding error accumulates.

With this in mind, the following results are not too surprising, as 0.5f has an exact representation as a binary floating-point number. So, there are no rounding errors.

m_signalColl.push_back(Signal{3, 0, calculateZero(2.5f, 0.5f, 5), 5}); // m_signalColl[0].factor == 0.0f updateSignalValue(3, 7); // m_signalColl[3].value == 7 updateSignalValue(2, 4); // m_signalColl[3].value == 4

One thing should be more than clear by now: The function `updateSignalValue()`

does not work properly in general. It may work under certain assumptions, but it doesn’t state them.

That was the point when I tried to figure out the possible values of `signal.factor`

. It was quickly clear that only positive integers (non-zero) were used. Therefore, the best remedy is to change the type of the field `factor`

in the structure `Signal`

from `float`

into `int`

. The non-zero check in `updateSignalValue()`

if (signal.factor != 0) {

becomes meaningful and unambiguous. And, the function `updateSignalValue()`

clearly states all of its assumptions. There is no need for anyone to figure them out.

This change has another positive effect. The division of `rawValue`

by `signal.factor`

becomes a true integer division. Before the change, it divided an integer by a float. This implied a couple of implicit conversions. `rawValue`

and `signal.factor`

were converted into doubles to be able to calculate the result of the division `rawValue / signal.factor`

as a double. This double was finally converted into an int. That was a lot of needless conversions, which were a strong hint that `signal.factor`

should always have been an integer.

So, the answer to the question whether a float variable is equal to 0.0f is: sometimes yes, sometimes no. Checking for equality or inequality with 0.0f is most often a not so pleasant smell that something is wrong with our code. So, we should analyse the problem and fix the root cause for the smell.