Because they are, for most purposes, more accurate than integers.
Now how is that? "for speed of an object in a game..." this is a good example for such a case. Say you need to have some very fast objects, like bullets. To be able to describe their motion with integer speed variables, you need to make sure the speeds are in the range of the integer variables, that means you cannot have an arbitrarily fine raster.
But then, you might also want to describe some very slow objects, like the hour hand of a clock. As this is about 6 orders of magnitude slower than the bullet objects, the first ld(10⁶) ≈ 20 bits are zero, that rules out short int
types from the start. Ok, today we have long
s everywhere, which leave us with a still-comfortable 12 bits. But even then, the clock speed will be exact to only to four decimal places. That's not a very good clock... but it's certainly ok for a game. Just, you would not want to make the raster much coarser than it already is.
...which leads to problems if some day you should like to introduce a new, even faster type of object. There is no "headroom" left.
What happens if we choose a float type? Same size of 32 bits, but now you have a full 24 bits of precision, for all objects. That means, the clock has enough precision to stay in sync up-to-the-seconds for years. The bullets have no higher precision, but they only "live" for fractions of a second anyway, so it would be utterly useless if they had. And you do not get into any kind of trouble if you want to describe even much faster objects (why not speed of light? No problem) or much slower ones. You certainly won't need such things in a game, but you sometimes do in physics simulations.
And with floating-point numbers, you get this same precision always and without first having to cleverly choose some non-obvious raster. That is perhaps the most important point, as such choice necessities are very error-prone.
I side with OP's personal theory that it is not a normal practice to allow a computer program to proceed with a division-by-zero operation, or to only perform a minimal check before the division.
The exception is when you are implementing something that is too general - a programming language (such as MATLAB) where you (as the programmer) do not know the context / application / use-case / physical meaning of the mathematical operations it is asked to perform. This may be because the formula it is evaluating is provided by the customer, and you do not know the customer's use-case of that formula. In that case you use a special representation such as Inf or NaN as a placeholder.
If, however, the formula is provided as part of a statistical toolbox, then you should be able to provide an explanation when the situation arises. See the "weighted averaging when the total weight is zero" example below.
There is a way to "invert" a divisor underflow test. Mathematically if b
is not zero, and
abs(a) / abs(b) > abs(c)
where c
is the largest representable floating point value, then
abs(a) > abs(c) * abs(b)
. However, in practice it requires a more careful implementation than that.
You may be able to find a mathematical library function that allows you to pass in (a, b)
and it will return whether the division will overflow, underflow, or otherwise have poor precision.
Source code analyzers look for patterns in the code; they are not sophisticated enough to decide whether someone's workaround logic is sufficient for the application's design purpose. (In fact even the average programmer may be unqualified to make that decision.) Source code analyzers are supposed to be augmented with a person qualified to make that decision.
A denominator of zero can occur in a lot of mathematical manipulations: formulas, infinite series (summation of sequence), etc. There are many mathematical methods to calculate the result despite having denominators that approach zero (i.e. not exactly zero, but are smaller than the machine-representable value). These means the formulas are not to be evaluated verbatim - they are transformed using some calculus methods, and for each formula there may be several alternative versions which is chosen to avoid the division-by-zero issue.
Another situation arises in weighted averaging of data. If you perform a query that selects a subset of data, and when:
- the sum of weights for the subset of data turns out to be zero, or
- when the subset is indeed empty, i.e. the query returns no result
then the proper way to phrase that situation is "insufficient samples (data) for the query", etc.
In basic trigonometry, some representations (slope) are very sensitive to division problems, whereas an alternative representation (bearing, i.e. angle) would not be sensitive. For example, to represent a line on a 2D plane, where vertical and near-vertical lines need to be represented as robustly as horizontal and near-horizontal lines, you can:
- have a toggle between lines that are steep vs. those that are not. For lines steeper than 45 degrees, you would use
(x / y)
instead of (y / x)
as the "flipped" slope of the line, so as to avoid the division by small numbers.
- Use an alternative representation such as
a*x + b*y + c == 0
and store the parameters (a, b, c)
with the requirement that (a^2 + b^2)
must equal 1.0
for normal case, and 0.0
if the line is degenerate (not-a-line).
It is worth mentioning that degeneracy is unavoidable in many different contexts (and in context-specific ways). For example, if user passes in a "line" from point (x1, y1) to point (x2, y2)
and asks to calculate its slope, and it happens that (x1 == x2 and y1 == y2)
, then there is no slope, because there is no line, because there is only a single point in the user's input.
Best Answer
You need to keep in mind that in FPU arithmetics, 0 doesn't necessarily has to mean exactly zero, but also value too small to be represented using given datatype, e.g.
a is too small to be represented correctly by float (32 bit), so it is "rounded" to -0.
Now, let's say our computation continues:
Because a is float, it will result in -infinity which is quite far from the correct answer of -1000000000000000000.0
Now let's compute b if there's no -0 (so a is rounded to +0):
The result is wrong again because of rounding, but now it is "more wrong" - not only numerically, but more importantly because of different sign (result of computation is +infinity, correct result is -1000000000000000000.0).
You could still say that it doesn't really matter as both are wrong. The important thing is that there are a lot of numerical applications where the most important result of the computation is the sign - e.g. when deciding whether to turn left or right at the crossroad using some machine learning algorithm, you can interpret positive value => turn left, negative value => turn right, actual "magnitude" of the value is just "confidence coefficient".