DEV Community

Cover image for When numbers don't make sense
MiguelMJ
MiguelMJ

Posted on • Updated on • Originally published at blog.miguelmj.dev

When numbers don't make sense

Introduction

Most of the time, basic arithmetic is pretty simple and intuitive. Even working with powers and roots isn't that much of a headache. However, the IEEE standard for floating-point numbers includes special values that can mess things up: NaN (Not a Number), positive infinity and negative infinity. In fact, most algebraic properties of real numbers are no longer valid. The only purpose of this post is to show some cases I found interesting, where these values produce counterintuitive results.

Cover image by Armand Khoury from Unsplash

Standard behavior of NaN and Infinity

  • For all x: NaN > x is false.

  • For all x: NaN < x is false.

  • For all x: NaN == x is false.

  • For all x: NaN != x is true.

  • For all x except NaN and +Infinity: +Infinity > x is true.

  • For all x except Nan and -Infinity: -Infinity < x is true.

  • Infinity / Infinity = NaN

  • Infinity * 0 == NaN

  • Infinity - Infinity == NaN

  • Any other arithmetic operation between a number and Infinity results in Infinity (with a possible change of sign).

  • Any other arithmetic operation between a number (or Infinity) and NaN results in NaN.

Everything I will tell you from now is trivially deduced from understanding the list above; in other words, if you understand all its implications, I won't tell you much new. Anyway, I think it is interesting to review some side effects that may escape from us when we don't keep them in mind.

Broken properties

The following is a non-exhaustive list of properties that we use to expect while programming, but are no longer true due to the existence of NaN and Infinity. I will provide the statement of the property (which doesn't hold) and then a piece of code in JavaScript and Python that implements a check for it, followed by a call that returns false.

Note: JavaScript produces NaN and Infinity values easily, not only because they are native values but because they result from regular operations (Math.sqrt(-1) or 1/0). In the case of Python, they are implemented in the math library (math.nan and math.inf) and operations that would produce them (math.sqrt(-1) or 1/0) will raise an exception. For this reason, assume the following header in each Python snippet.

from math import nan, inf
Enter fullscreen mode Exit fullscreen mode

Disclaimer: The snippets of JavaScript will also fail with values like strings or objects. I'm not trying to make any point about errors in JavaScript. The point is about the NaN and Infinity values, which belong to numeric types in any language with full support for the IEEE standard.


Now, behold the fall of the tautologies.

Identity

Let's start with the most obvious and painful.

function identity(x){
    return x == x
}
identity(NaN)
Enter fullscreen mode Exit fullscreen mode
def identity(x):
    return x == x

identity(nan)
Enter fullscreen mode Exit fullscreen mode

Some of the following are direct results of this.

A list can't contain elements that aren't equal to any element

Sorry for the apparent redundancy in the statement: check the examples to see what I mean.

function checkIncludesEqual(arr, x){
    if(arr.includes(x)){
        return arr.some(e => e == x)
    }else{
        return true
    }
}

checkIncludesEqual([1, 2, NaN], NaN)
Enter fullscreen mode Exit fullscreen mode
def checkIncludesEqual(arr, x):
    if x in arr:
        return any(x == e for e in arr)
    else:
        return True

checkIncludesEqual([1, 2, nan], nan)
Enter fullscreen mode Exit fullscreen mode

The max value of a list is greater than or equal to the rest

function checkMaxIsGE(arr){
    let maxVal = Math.max(...arr)
    return arr.every(x => maxVal >= x)
}
checkMaxIsGE([-1, 0, 1, NaN])
Enter fullscreen mode Exit fullscreen mode
def checkMaxIsGE(arr):
    maxVal = max(arr)
    return all(maxVal >= x for x in arr)

checkMaxIsGE([-1, 0, 1, nan])
Enter fullscreen mode Exit fullscreen mode

The min value of a list is lesser than or equal to the rest

function checkMinIsLE(arr){
    let minVal = Math.min(...arr)
    return arr.every(x <= minVal >= x)
}
checkMinIsLE([-1, 0, 1, NaN])
Enter fullscreen mode Exit fullscreen mode
def checkMinIsLE(arr):
    minVal = min(arr)
    return all(minVal <= x for x in arr)

checkMinIsLE([-1, 0, 1, nan])
Enter fullscreen mode Exit fullscreen mode

Dividend equals divisor times quotient

This is the first we learn about division. D / d = Q implies D = Q * d. Well... no more. This example can only be shown in JavaScript because Python prevents division by zero.

function checkDdQ(dividend, divisor){
    quotient = dividend / divisor
    return dividend == quotient * divisor
}
checkDdQ(1, 0)
checkDdQ(1, Infinity)
Enter fullscreen mode Exit fullscreen mode

The square of a value is either greater than or equal to itself or less than one

function checkSquareIsGE(x){
    return x*x >= x ||  x*x < 1
}
checkSquareIsGE(NaN)
Enter fullscreen mode Exit fullscreen mode
def checkSquareIsGE(x):
    return x*x >= x || x*x < 1

checkSquareIsGE(nan)
checkSquareIsGE(inf)
Enter fullscreen mode Exit fullscreen mode

"Less than or equal to" is equivalent to "Not greater than"

I find this particularly disturbing, not sure why.

function checkLEQisNGT(x){
    return (x <= 10) == !(x > 10)
}
checkLEQisNGT(NaN)
Enter fullscreen mode Exit fullscreen mode
def checkLEQisNGT(x):
    return x <= 10 and not x > 10

checkLEQisNGT(nan)
Enter fullscreen mode Exit fullscreen mode

A value divided by itself equals one

function checkBetweenSelfIsOne(x){
    return x/x == 1
}
checkBetweenSelfIsOne(NaN)
checkBetweenSelfIsOne(Infinity)
Enter fullscreen mode Exit fullscreen mode
def checkBetweenSelfIsOne(x):
    return x/x == 1

checkBetweenSelfIsOne(nan)
checkBetweenSelfIsOne(inf)
Enter fullscreen mode Exit fullscreen mode

A value minus itself equals zero

function checkMinusSelfIsZero(x){
    return x-x == 0
}
checkMinusSelfIsZero(NaN)
checkMinusSelfIsZero(Infinity)
Enter fullscreen mode Exit fullscreen mode
def checkMinusSelfIsZero(x):
    return x-x == 0

checkMinusSelfIsZero(nan)
checkMinusSelfIsZero(inf)
Enter fullscreen mode Exit fullscreen mode

A value times zero equals zero

function checkTimesZero(x):
    return x * 0 == 0

checkTimesZero(NaN)
checkTimesZero(Infinity)
Enter fullscreen mode Exit fullscreen mode
def checkTimesZero(x):
    return x * 0 == 0

checkTimesZero(nan)
checkTimesZero(inf)
Enter fullscreen mode Exit fullscreen mode

These last two have an interesting side effect too. One would expect that 1 / (x-x) and 1 / (x * 0) to result in Infinity. However, when x is Infinity, they result in NaN. Don't you feel dizzy yet?

Bonus fun fact!

Hey, while working on this article I found something interesting about square roots in Python. As I stated earlier, Python raises an exception if you try to compute the square root of a negative number. But Python implements complex numbers natively so if you use the power operator (**) instead, it returns a valid result!

>>> from math import sqrt
>>> sqrt(-1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error
>>> (-1) ** 0.5
(6.123233995736766e-17+1j)
Enter fullscreen mode Exit fullscreen mode

The perfect result would have been (0+1j) but due to floating-point precision issues, instead of 0 you have a very, very small number. I hope you found it interesting too!

Conclusion

Semantically sound code is much less prone to errors and reduces the need to be constantly catching exceptions. I hope these examples have served as a reminder that we cannot neglect special numeric values or take for granted many seemingly intuitive properties that, if overlooked, can cause apparently correct code to exhibit undesired behavior.

Latest comments (3)

Collapse
 
ssinad profile image
Sina D

The square of any number between zero and one is smaller than the number itself. It is not a tautology.

Collapse
 
miguelmj profile image
MiguelMJ

You are right!! I will edit as soon as possible to fix that. Thanks a lot ❤️

Collapse
 
miguelmj profile image
MiguelMJ

I extended it to "The square of a value is either greater than or equal to itself or less than one". Now I think it is a tautology.

I hope you liked the rest of the post :)