rationale for math.Max(1, math.NaN()) => math.NaN()?

214 views
Skip to first unread message

Dan Kortschak

unread,
Apr 22, 2021, 5:54:55 AM4/22/21
to golang-nuts
This is not something that I've thought about before, but the behaviour
of math.Max when only one of its arguments is NaN does not agree with
the C convention or the IEEE-754 standard behaviour for max (5.3.1 p19
"maxNum(x, y) is the canonicalized number y if x<y, x if y<x, the
canonicalized number if one operand is a number and the other a quiet
NaN.").

Does anyone know the reason for this? (Looking through other languages,
the situation in general seems to be a mess).

e.g. in C

~ $ cat n.c
#include <math.h>
#include <stdio.h>

void main() {
printf("%f\n", fmax(1, nan("")));
}
~ $ gcc -o a.out n.c
~ $ ./a.out
1.000000

and in Go

~ $ cat n.go
package main

import (
"fmt"
"math"
)

func main() {
fmt.Println(math.Max(1, math.NaN()))
}
~ $ go run n.go
NaN


Jesper Louis Andersen

unread,
Apr 22, 2021, 6:43:23 AM4/22/21
to Dan Kortschak, golang-nuts
On Thu, Apr 22, 2021 at 11:54 AM 'Dan Kortschak' via golang-nuts <golan...@googlegroups.com> wrote:

Does anyone know the reason for this? (Looking through other languages,
the situation in general seems to be a mess).


I looked at OCaml, which provides both variants:

Float.max : float -> float -> float
Float.max_num : float -> float -> float

The `max` function includes NaNs in the computation, while `max_num` omits them. Another important treatment the documentation mentions is `max -0.0 +0.0`.

I also looked at Matlab and R. They essentially provide ways to either include the NaN or omit it from the computation, usually when you have a vector of numbers where some of the numbers are unknown. I.e., they take extra (optional) flags to their functions which tells you how to treat NaN values.

The "pure" solution is clearly the one where NaN's yield a NaN. It is isomorphic to a Monoid based on optional values with the NaN forming the unknown element, often called the option-monoid or the maybe-monoid. Or said otherwise: for any type a, where a is a monoid, so is 'option a':

forall a. Monoid a => Monoid (Option a)

Since the max function itself is a monoid with the neutral element taken as the minimal value, -inf, we can thus "lift" NaN values on top and still have some mathematical consistency.

But if you have an array of values, where some of them can be NaN, it is probably smart to provide a function which filters the array of NaN values and computes the remaining values. R provides the function `na.omit(..)` for this. So do option types in languages such as OCaml or Haskell. Here, we are not that concerned about what is sound from a formal point of view, but more concerned with data cleanup before we start using the formality.

Max of two values is a special case. The generalized version takes an array/vector of values and provides the maximum in the structure.

All of this leads me to conclude you probably want both variants in the end. In some situations, it is better to include NaN values in the computation, and in other situations, the right move is to omit/exclude them.


christoph...@gmail.com

unread,
Apr 27, 2021, 2:28:39 AM4/27/21
to golang-nuts
It seam that C is wrong on this one and Go is right. The rationale is that a NaN must propagate through operations so that we can detect problems (avoid silent NaNs). See https://en.wikipedia.org/wiki/NaN

Thus any operation involving a NaN must return an NaN and this includes Max and Min. 

Dan Kortschak

unread,
Apr 27, 2021, 2:49:18 AM4/27/21
to golan...@googlegroups.com
On Mon, 2021-04-26 at 23:28 -0700, christoph...@gmail.com wrote:
> It seam that C is wrong on this one and Go is right. The rationale is
> that a NaN must propagate through operations so that we can detect
> problems (avoid silent NaNs). See https://en.wikipedia.org/wiki/NaN
>
> Thus any operation involving a NaN must return an NaN and this
> includes Max and Min.

This is not what the IEEE 754 spec says for the behaviour of minNum and
maxNum. Whether a language decides to implement their min and max
functions according to the IEEE 754 spec is up to them. Note that Go
uses non-signalling NaN's and claims to use IEEE 754 floating point
numbers (https://golang.org/ref/spec#Numeric_types). So we'd expect it
to have the behaviour specified in the 754 spec. This is why I asked
the question. Jesper's answer highlighted subtlety that is worth
considering.


Bakul Shah

unread,
Apr 27, 2021, 2:56:20 AM4/27/21
to golang-nuts
From the wikipedia article:

There are differences of opinion about the proper definition for the result of a numeric function that receives a quiet NaN as input. One view is that the NaN should propagate to the output of the function in all cases to propagate the indication of an error. Another view, and the one taken by the ISO C99 and IEEE 754-2008 standards in general, is that if the function has multiple arguments and the output is uniquely determined by all the non-NaN inputs (including infinity), then that value should be the result.

Should Go be updated to follow IEEE 754-2008? [I assume currently it is following IEEE 754]

-- Bakul

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/9950a06b-1222-4543-b979-763f37516e2an%40googlegroups.com.

Brian Candler

unread,
Apr 27, 2021, 2:59:13 AM4/27/21
to golang-nuts
On Tuesday, 27 April 2021 at 07:28:39 UTC+1 christoph...@gmail.com wrote:
It seam that C is wrong on this one and Go is right. The rationale is that a NaN must propagate through operations so that we can detect problems (avoid silent NaNs). See https://en.wikipedia.org/wiki/NaN

Thus any operation involving a NaN must return an NaN and this includes Max and Min. 
 

That Wikiepedia page has a paragraph about this specific case:

In section 6.2 of the old IEEE 754-2008 standard, there are two anomalous functions (the maxNum and minNum functions, which return the maximum of two operands that are expected to be numbers) that favor numbers — if just one of the operands is a NaN then the value of the other operand is returned. The IEEE 754-2019 revision has replaced these functions as they are not associative (when a signaling NaN appears in an operand).[4][5]

Reference [4] is:  David H.C. Chen (21 February 2017). "The Removal/Demotion of MinNum and MaxNum Operations from IEEE 754™-2018" (PDF). It very clearly describes the problem and gives the behaviours of some selected implementations.

Given that this was only fixed in 2019, you can hardly blame C for getting it wrong :-)
Reply all
Reply to author
Forward
0 new messages