Remix.run Logo
exDM69 3 days ago

My favorite thing about floating point numbers: you can divide by zero. The result of x/0.0 is +/- inf (or NaN if x is zero). There's a helpful table in "weird floats" [0] that covers all the cases for division and a bunch of other arithmetic instructions.

This is especially useful when writing branchless or SIMD code. Adding a branch for checking against zero can have bad performance implications and it isn't even necessary in many cases.

Especially in graphics code I often see a zero check before a division and a fallback for the zero case. This often practically means "wait until numerical precision artifacts arise and then do something else". Often you could just choose the better of the two options you have instead of checking for zero.

Case in point: choosing the axes of your shadow map projection matrix. You have two options (world x axis or z axis), choose the better one (larger angle with viewing direction). Don't wait until the division goes to inf and then fall back to the other.

[0] https://www.cs.uaf.edu/2011/fall/cs301/lecture/11_09_weird_f...

kccqzy 2 days ago | parent | next [-]

This is also my favorite thing about floating point numbers. Unfortunately languages like Python try to be smart and prevent me from doing it. Compare:

    >>> 1.0/0.0
    ZeroDivisionError
    >>> np.float64(1)/np.float64(0)
    inf
I'm so used to writing such zero division in other languages like C/C++ that this Python quirk still trips me up.
pklausler 2 days ago | parent [-]

Division by zero is an error and it should be treated as such. "Infinity" is an error indication from overflow and division by zero, nothing more.

sfpotter 2 days ago | parent | next [-]

This is totally false mathematically. Please look up the extended real number system for an example. Many branches of mathematics affix infinity to some existing number system, extending its operations consistently, and do all kinds of useful things with this setup. Being able to work with infinity in exactly the same way in IEEE754 is crucial for being able to cleanly map algorithms from these domains onto a computer. If dividing by zero were an error in floating point arithmetic, I would be unable to do my job developing numerical methods.

ForceBru 2 days ago | parent | prev [-]

The article's point 3 says that this is a myth. Indeed, the _limit_ of `1/x` as `x` approaches zero from the right is positive infinity. What's more, division by _negative zero_ (which, perhaps surprisingly, is a thing) yields negative infinity, which is also the value of the corresponding limit. If you divide a finite float by infinity, you get zero, because `lim_{x\to\infty} c/x=0`. In many cases you can treat division by zero or infinity as the appropriate limit.

pklausler 2 days ago | parent [-]

I am allowed to disagree with the article.

ForceBru 2 days ago | parent | next [-]

Sure, but it makes sense, doesn't it? Even `inf-inf == NaN` and `inf/inf == NaN`, which is true in calculus: limits like these are undefined, unless you use l'Hôpital's rule or something. (I know NaN isn't equal to itself, it's just for illustration purposes) But then again, you usually don't want these popping up in your code.

pklausler 2 days ago | parent [-]

In practice, though, I can't recall any HPC codes that want to use IEEE-754 infinities as valid data.

afiori 2 days ago | parent [-]

A significant chunk of floating point bitspace is dedicated to NaNs to represent explicitly invalid data (mostly I suppose to reduce the need for branches and tests in HPC) and NaNs even break the reflexivity of equality in many languages compared to them negative 0 and positive/negative infinity are perfectly valid

2 days ago | parent | prev [-]
[deleted]
thwarted 3 days ago | parent | prev | next [-]

You still can't divide by zero, it just doesn't result in an error state that stops execution. The inf and NaN values are sentinel values that you still have to check for after the calculation to know if it went awry.

sixo 3 days ago | parent | next [-]

In the space of floats, you are dividing by zero. To map back to the space of numbers you have to check. It's nice, though; inf and NaN sentinels give you the behavior of a monadic `Result | Error` pipeline without having to wrap your numbers in another abstraction.

epcoa 3 days ago | parent | prev | next [-]

If dividing by zero has a well defined result that doesn’t abort execution what exactly does “can’t” even mean?

Operations on those sentinel values are also defined. This can affect when checking needs to be done in optimized code.

bee_rider 2 days ago | parent [-]

I believe divide-by-zero produces an exception. The machine can either be configured to mask that exception, or not.

Personally, I am lazy, so I don’t check the mxcsr register before I start running my programs. Maybe gcc does something by default, I don’t know. IMO legitimate division by zero is rare but not impossible, so if you do it, the onus is on you to make sure the flags are set up right.

epcoa 2 days ago | parent [-]

Correct, divide by zero is one of the original five defined IEEE754-1985 exception. But the default behavior then and now is to produce that defined result mentioned and continue execution with a flag set ("default non-stop"). Further conforming implementations also allow "raiseNoFlag".

It's well-defined is all that really matters AFAIC.

exDM69 2 days ago | parent | prev | next [-]

It's not always necessary to check for inf/NaN explicitly using isinf/isnan. Both inf and NaN are floating point values with well defined semantics.

I'll give two examples from a recent project where I very intentionally divided by zero. First one was about solving a zero in a derivative and check if it falls on 0..1 range. This exploits the fact that (x < NaN) is always false and comparisons with +/- inf behave as expected.

    float t = a / b; // might divide by zero. NaN if a and b == 0.0, +/- inf if b == 0.0
    if (t > 0.0 && t < 1.0) {
        // we don't get here if t is +/- inf or nan
        split_at(t); // do the thing
    }
The second one was similar, but clamping to 0..1 range using branchless simd min/max.

    f32x8 x = a / b; // might divide by zero
    return simd_min(simd_max(0.0, x), 1.0); // returns 0 for -inf or nan, 1 for +inf
In both of these cases, explicitly checking for division by zero or isinf/isnan would've been (worse than) useless because just using the inf/NaN values gave the correct answer for what comes next.
mike_ivanov 3 days ago | parent | prev | next [-]

This is the Result monad in practice. It allows you to postpone error handling until the computation is done.

wpollock 2 days ago | parent | prev | next [-]

It has always amused me that Integer division by 0 results in "floating point exception", but floating point division by 0.0 doesn't!

TehShrike 3 days ago | parent | prev [-]

You can divide by zero, but you mayn't.

layer8 2 days ago | parent | prev | next [-]

Floating-point arithmetics adopted this from the extended reals (usually denoted as ℝ̅): https://en.wikipedia.org/wiki/Extended_real_number_line (see Arithmetic operations there)

wolvesechoes 2 days ago | parent | prev [-]

> you can divide by zero

It is implementation-dependent. It is not obligatory for implementation to respect IEEE 754.