4
$\begingroup$

Sometimes, we want to implement floating-point operations on systems which use IEEE 754 representations of values, but don't necessarily support IEEE 754 semantics. For example, we may be using the -ffast-math compiler flag, or working with GPUs, DSPs, or other coprocessors. Additionally, suppose we lack NaNs or otherwise choose not to represent them.

Even with denorms, zero is somewhat fuzzy, in the sense that (positive) zero represents both itself and also extremely small (positive) numbers. Similarly, infinity is somewhat fuzzy, in the sense that all overflowing computations round up to infinity.

This suggests a fun answer to the question of division by zero. Define division of X and Y as multiplication of X and Y's multiplicative inverse, and define zero and infinity as multiplicative inverses. Then, division by zero is multiplication by infinity.

Immediately, 0 / 0 = 0 × ∞ = 1, which might not be desirable; what else breaks or is counterintuitive?

$\endgroup$
17
  • 5
    $\begingroup$ This is really a mathematics question. What goes wrong is that certain mathematical properties (e.g. distributivity of multiplication over addition) cease to hold. I don't think programming language design expertise is relevant to answering. $\endgroup$
    – kaya3
    Dec 2, 2023 at 23:38
  • 2
    $\begingroup$ I don't understand how you conclude that "0 × ∞ = 1". $\endgroup$ Dec 3, 2023 at 0:18
  • 1
    $\begingroup$ @LazarLjubenović: Multiplicative inverses should always multiply to approximately 1. For example, 0.5 × 2 = 1 in typical floating-point implementations. $\endgroup$
    – Corbin
    Dec 3, 2023 at 17:29
  • 3
    $\begingroup$ But ∞ is not a number and it's not a multiplicative inverse of 0. I mean, you can define it like that in your own nomenclature, but then you have to deal with consequences of other common terms not making sense anymore. It's impossible to redefine just one mathematical concept and expect that everything else will just work out on its own. $\endgroup$ Dec 3, 2023 at 18:35
  • 1
    $\begingroup$ @Corbin The question title seems to differ from the question body. When does replacing division by zero with multiplication by infinity in an IEEE-754 compatible environment return a different result (not exception flags)? It seems to me there is no such case, and this transformation is valid under "as-if" semantics. The question body however asks what would happen if NaN support is removed (so no longer IEEE-754 compatible), which is a different issue. $\endgroup$
    – njuffa
    Dec 4, 2023 at 8:02

4 Answers 4

10
$\begingroup$

What goes wrong when division-by-zero is defined as multiplication-by-infinity?

Nothing. Division by zero already always has the exact same result as multiplication by infinity. For any non-zero a, you have a / 0 = ∞ already, so redefining it as a * ∞ doesn't change the result because that's already . For zero, 0 / 0 is normally NaN, and applying your definition again doesn't change anything because 0 * ∞ is also NaN already.

The problems start when you (A) remove NaN from the equation, and with the attempt to solve it by defining two additional things: (B) division of X and Y as multiplication of X and Y's multiplicative inverse, and (C) define zero and infinity as multiplicative inverses.

What else breaks or is counterintuitive?

Furthermore, ∞ - ∞ would normally return NaN, but that value's banned, so we need to come up with a solution for that as well. Following your line of thought, we define (D) the difference of X and Y as a sum of X and Y's additive inverse, and (E) define positive and negative infinity as additive inverses. Then, subtracting a positive infinity is adding a negative infinity. Immediately, ∞ - ∞ = ∞ + (-∞) = 0. Just like 0 × ∞ = 1, this is undesirable.

The problem with doing things like this is that now you also have to accept things like 0 = 0 * 1 = 0 * (0 / 0) = (0 * 0) / 0 = 0 / 0 = 1, breaking associative and distributive laws. Granted, these laws don't fully hold in IEEE-754 due to rounding errors, but the margin of error is never 1.

You could say you're fine with margin of error being 1. After all, what's a single 1 compared to the infinite ∞? Well, actually... ∞ = ∞ * 1 = ∞ * (∞ / ∞) = (∞ * ∞) / ∞ = ∞ / ∞ = 1.

So we end up with 0 = 1 = ∞ all being the same -- these are the exact things we've tried to mess with by tweaking definitions.

Interestingly, they actually ALL mean NaN now, i.e. a value whose value we cannot rely on to conclude anything meaningful -- if the result of an operation is either of these three, we must discard it as it has possibly gone through some of the nonsensical transformations like above. Our attempt to remedy removal of NaN has led us to losing three pretty important values that would otherwise be useful and sensible.

In a sense, both and NaN are kind of "not a number"s, but we can attach a meaningful distinction between different kind of "not a number"s, hence we normally want both and NaN.


Let's tweak your starting reasoning a bit: instead of immediately focusing on the concepts of "infinity" and "zero", let's take a step back: why does infinity even exist? One of the most obvious reasons is because we've introduced division -- you need a way to represent a / 0. So one way to simplify IEEE-754 is to disallow division completely. Now you have no NaN and no . Yay!

If you still want division, you must somehow handle a / 0 (assuming you want a zero as a valid value). One way is to define that as (mostly) , but you can just cut your losses here and declare that a NaN instead. Division by zero, including 0 / 0 is simply NaN in this case. You still have to define how comparing with NaN works, etc. But hey, no .

...Except even that is a great simplification. Now the compiler needs to reject the 99e9999 literal, because that would normally be represented as . The runtime would also need to do something when you try to multiply the largest representable number by two. You can argue that it can just return the largest number back, but then you're basically just avoiding using the word infinity, as your largest number is now actually .


That said, I don't think the idea of getting rid of NaN is bad in a programming language that uses IEEE-754 under the hood. You can hide NaN behind a first-class exception system, or a Maybe/Option system, or re-use the null if you have it, or give it a special type so the author is forced to handle the NaN case explicitly. NaN exists in IEEE-754 because it must somehow represent every possible combination of operands and operations. A format cannot throw an exception -- but your language can. Now if that is a good idea is a whole other can of worms.

$\endgroup$
3
  • 1
    $\begingroup$ As you say, some of your intermediate derivations are unsupported by typical floating-point lack of associativity. The derivation of ∞ - ∞ = 0 is valid, though, and certainly has to be accepted as part of what goes wrong. $\endgroup$
    – Corbin
    Dec 5, 2023 at 16:39
  • $\begingroup$ Note that, due to how overflow works already, ∞ is the margin of error for reassociating or distributing multiplication. This is part of why Kahan encourages not being "mindless" about roundoff error when optimizing a numerical expression; roundoff is not easily distinguishable from overflow. $\endgroup$
    – Corbin
    Dec 5, 2023 at 16:41
  • 1
    $\begingroup$ This kind of reminds me of the principle in pure mathematics that assuming that 0/0=1 leads to the zero ring—which is neither desirable not useful. $\endgroup$
    – Someone
    Dec 7, 2023 at 19:46
2
$\begingroup$

The main problem with implementing division as multiplication by the reciprocal is loss of accuaracy due to double rounding -- if both the reciprocal and mulitply give the closest answer that can be represented, the result of both may result in something that is off by 1ulp. This can be avoided by using a special representation for the intermediate value (the reciprocal result) with an extra bit of precision, or by using a dedicated division. Both are about as expensive, hardware-wise.

This is completely orthogonal to the question of infinity. If your number system implementation can represent infinity, then it probably makes sense to support some kind of NaN as well, and then it makes sense to define 0/0 == 0 × ∞ == NaN. If your number system does not support either, then division by 0 is just a problem needing some sort of error indication outside of the number system. Supporting ∞ and not NaN would still need some sort of error result for both 0/0 and 0 × ∞

$\endgroup$
4
  • 1
    $\begingroup$ I'm not downvoting this, although I don't feel like it's on the path to being accepted either. My main thought is that, well, I have implemented this as part of three raytracers in two languages, both having dedicated FP reciprocal operations and one language avoiding NaNs entirely. Replacing division by multiplication is a standard numerical-methods tactic, although it has the downsides you've correctly mentioned. $\endgroup$
    – Corbin
    Dec 4, 2023 at 8:04
  • $\begingroup$ " Supporting ∞ and not NaN would still need some sort of error result for both 0/0 and 0 × ∞" ─ the question asks why they can't just be defined as 1. Why is an error result needed instead? $\endgroup$
    – kaya3
    Dec 4, 2023 at 18:12
  • $\begingroup$ @kaya: While you could do that, it is akin to defining 2 + 2 = 5 -- pretty much nonsense. "What would go wrong" is any calculation involving 0/0 or 0 × ∞ $\endgroup$
    – Chris Dodd
    Dec 4, 2023 at 19:36
  • 2
    $\begingroup$ Of course I agree, but "if you do that then people on Langdev SE will call it nonsense" isn't a very satisfactory answer to "what goes wrong". $\endgroup$
    – kaya3
    Dec 4, 2023 at 19:52
1
$\begingroup$

One thing that breaks is compatibility between floating-point arithmetic and integer arithmetic.

Most languages have a float and an int type, and the float type has nan and inf and -inf values, but the int type doesn't. Does this mean that all three of 1.0 / 0.0, 1 / 0.0 and 1.0 / 0 would return inf, but 1 / 0 would raise a "division by zero" error because int has no infinity?

Moreover, extending a system of arithmetic to define the result of 1 / 0 to be makes sense in a small number of contexts, but doesn't make sense in most contexts; it's probably better to warn the programmer of a potential error by raising a "division by zero" error in all contexts, and let the programmer handle the special case if (divisor == 0) { something that makes sense in this particular context }.

For instance, the mean of a list is sum(list) / length(list). With your fix, programs that compute the mean of a list would unhelpfully return an infinite mean in the case of empty lists, instead of warning the programmer that they forgot to handle a special case.

$\endgroup$
-2
$\begingroup$

You assume that there is no NaN. Without NaN there is no reasonable result for 0.0 / 0.0 whatsoever. There is also no reasonable result for 0 x infinity.

$\endgroup$
1
  • 4
    $\begingroup$ The question proposes that the result would be 1, and asks what goes wrong. It may very well be that 1 is not a "reasonable" result, but simply saying so doesn't answer the question. $\endgroup$
    – kaya3
    Dec 3, 2023 at 20:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .