Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#9530 closed bug (invalid)

min / max do not always return the other argument when one of the arguments is NaN

Reported by: jrp Owned by:
Priority: normal Milestone:
Component: Prelude Version: 7.8.3
Keywords: Cc:
Operating System: MacOS X Architecture: Unknown/Multiple
Type of failure: Incorrect result at runtime Test Case:
Blocked By: #9276 Blocking:
Related Tickets: 9276 Differential Rev(s):
Wiki Page:

Description

I assume that this is well-known, but it tripped me up:

Prelude> let inf = 1/0
Prelude> let nan = 0/0
Prelude> min nan  inf
Infinity
Prelude> min inf nan
NaN
Prelude> min 3 nan
NaN
Prelude> min nan 3
3.0
Prelude> max nan inf
NaN
Prelude> max inf nan
Infinity
Prelude> max 3 nan
3.0
Prelude> max nan 3
NaN

Change History (15)

comment:1 Changed 5 years ago by jrp

... also signum NaN returns -1.0, rather than NaN

comment:2 Changed 5 years ago by carter

Blocked By: 9276 added
Resolution: invalid
Status: newclosed

thanks for reporting that you found these behaviors confusing, because they are! (though they are totally kosher with respect to the IEEE Floating point semantics, as well as the Ord instance for floating point numbers, so technically not a bug, but rather a distasteful behavior).

thus while it IS an unexpected behavior, its not a bug ( try doing [1.07 :: Float .. 9.9]in ghci to see something truly bonkers that ),

Its worth noting that the ieee standard specifies an even worse behavior for min and max, that when only one arg is NAN, return the *other* (not NAN) argument! (though the historical motivation is a bit suspect.)

In fact, i've been working on writing up a proposal to change the semantics of min and max on floating point values! (should happen some time after ICFP is over this week). I spoke with a number of the Julia lang folks about this issue (which was raised with them after I discussed min/max with a member of their community), and I'll likely propose to change the floating point min / max to have the same semantics as their change https://github.com/JuliaLang/julia/issues/7866

their change is: 1) when either argument of min/max is NAN, return NAN 2) Except:

a) for min, when either argument is -\infinity, return - \infinity b) for max, when either argument is +\infinity, return +\infinity

that said, i'm letting the design / thinking about the implications bake before writing up a proposal that change (though it seems like the most reasonable possible definition given the constraints of Float and Double), because getting the semantics as right as possible matters, and id like to be confident in changing how math works for haskell before committing to the change.

I do think that this change in definition will get unanimous support, but i'm trying to also understand *WHAT* else can be fixed up in the same proposal that should be included along with that change.

comment:3 in reply to:  2 ; Changed 5 years ago by dfeuer

Replying to carter:

thanks for reporting that you found these behaviors confusing, because they are! (though they are totally kosher with respect to the IEEE Floating point semantics, as well as the Ord instance for floating point numbers, so technically not a bug, but rather a distasteful behavior).

thus while it IS an unexpected behavior, its not a bug ( try doing [1.07 :: Float .. 9.9]in ghci to see something truly bonkers that ),

Its worth noting that the ieee standard specifies an even worse behavior for min and max, that when only one arg is NAN, return the *other* (not NAN) argument! (though the historical motivation is a bit suspect.)

In fact, i've been working on writing up a proposal to change the semantics of min and max on floating point values! (should happen some time after ICFP is over this week). I spoke with a number of the Julia lang folks about this issue (which was raised with them after I discussed min/max with a member of their community), and I'll likely propose to change the floating point min / max to have the same semantics as their change https://github.com/JuliaLang/julia/issues/7866

their change is: 1) when either argument of min/max is NAN, return NAN 2) Except:

a) for min, when either argument is -\infinity, return - \infinity b) for max, when either argument is +\infinity, return +\infinity

that said, i'm letting the design / thinking about the implications bake before writing up a proposal that change (though it seems like the most reasonable possible definition given the constraints of Float and Double), because getting the semantics as right as possible matters, and id like to be confident in changing how math works for haskell before committing to the change.

I do think that this change in definition will get unanimous support, but i'm trying to also understand *WHAT* else can be fixed up in the same proposal that should be included along with that change.

I think it probably makes sense to have multiple sets of operations available:

  1. IEEE compliant stuff, whether sane or not.
  1. Stuff that's fast in hardware, whether sane or not.
  1. Stuff that's sane.

Your \pm\infty thing only makes a sort of sense for some NaN situations, I believe. I can buy max (1/0) (0/0) = Infinity, for example, but it's hard for me to make sense of max (1/0) (sqrt (-1)) being anything but NaN.

comment:4 Changed 5 years ago by carter

the ieee min/max behavior on nans is bonkers and useless for us.

@dfeuer happy to chat about this more via irc or email, but Floats aren't numbers, they just act like they are, but explaining their semantics in more detail and why/when ieee should be ignored or not is too complicated for trac tickets

comment:5 Changed 5 years ago by carter

most of the IEEE operations that have hardware support baked in are pretty reasonable and what ghc already does. min/max is one of the few operations where the ieee spec, the hardware supported spec, and the reasonable semantics specs are not the same.

comment:6 Changed 5 years ago by jrp

I don't have a copy of the standard, but http://en.wikipedia.org/wiki/IEEE_754_revision says

min(x,NaN) = min(NaN,x) = x
max(x,NaN) = max(NaN,x) = x

which is not satisfied here

Prelude> max 3 nan
3.0
Prelude> max nan 3
NaN

http://msdn.microsoft.com/en-gb/library/windows/desktop/jj218760(v=vs.85).aspx agrees:

The IEEE-754R specification for floating point min and max operations 
states that if one of the inputs to min or max is a quiet QNaN value, the result
 of the operation is the other parameter.

It goes on to add a further wrinkle:

A revision of the IEEE-754R specification adopted a different behavior for min 
and max when one input is a "signaling" SNaN value versus a QNaN value:

So far as I can tell, Haskell does not know anything about signalling NaNs, so this is probably not applicable.

I'll change the title of the ticket to make the issue clear.

comment:7 Changed 5 years ago by jrp

Resolution: invalid
Status: closednew
Summary: min / max do not always return a NaN when one of the arguments is NaNmin / max do not always return the other argument when one of the arguments is NaN

comment:8 Changed 5 years ago by carter

@jrp, in this case the IEEE standard doesn't make sense to comply with, if you read that julia lang ticket about the min/max behavior, theres an inline email by one of the IEEE spec authors about the motivation for the nan evading behavior.

though I do agree from a strict IEEE compliant behavior standpoint, it is nonconforming, this is a very clear example (perhaps the ONLY example) where min/max are better off being implemented in a different way. Haskell has no need for using Nan to pun representing missing data. (see that julia lang thread for a longer discussion on the matter)

comment:9 Changed 5 years ago by carter

(though if theres a good argument to the contrary, please share! :) )

comment:11 Changed 5 years ago by jrp

Thanks. I'll have a more detailed look. I can't, however, see any good reason for having min / max asymmetrical in their arguments, whether the result is to be NaN or the other argument.

The standard seems to agree. It says (at 5.3.1) that

minNum(x, y) is the canonicalized number x if x<y, y if y<x, the canonicalized number if one operand is a number and the other a quiet NaN. Otherwise it is either x or y, canonicalized (this means results might differ among implementations). When either x or y is a signalingNaN, then the result is according to 6.2.

6.2: ... For an operation with quiet NaN inputs, other than maximum and minimum operations, if a floating-point result is to be delivered the result shall be a quiet NaN which should be one of the input NaNs. ...

(I am ignoring signalling NaNs here as they don't seem to be relevant to Haskell.)

As things stand, Haskell sometimes returns NaN and sometimes the other argument, depending on the order in which the arguments are presented. I can't see how this can be right, What am I missing?

comment:12 Changed 5 years ago by carter

read what I was saying earlier, we agree the current situation is not satisfactory (and I am working on a proposal to make it better).

Interesting, the first version of the IEEE standard (the 1985 standard) doesn't seem to mention anything about min/max http://www.wellposed.com/standards-documents-cache/IEEE754-1985.pdf

point being, the challenge is to choose a definition that "works the most" subject to floating point being what it is. And the currently in ghc BASE is certainly confusing and odd, but that a direct consequence of Float/Double min/max using their ORD instance definition of compare to induce the min/max operations.

Thus, we need to balance a few tricky concerns (and definitional consistency!) when changin min/max, because if nothing else, the current behavior is exactly that induced by the defintion of compare on floating point.

Changing the Ord instance for Floating point is something that needs to be considered pretty thoughtfully (though I think theres a pretty clear story to make min/max return nan if either arg is nan, the trick question is how to handle the "laws" about min and max at the ±infinity points).

Thanks for doing all this leg work though! Whats your application domain that youre focusing on thats getting you to hit these issues? Theres a bunch of other numerical computing haskell folks on freenode IRC at #numerical-haskell who are a great resource for various numerical matters generally!

Last edited 5 years ago by carter (previous) (diff)

comment:13 Changed 5 years ago by carter

comment:14 in reply to:  3 Changed 5 years ago by rwbarton

Resolution: invalid
Status: newclosed

Replying to dfeuer:

I think it probably makes sense to have multiple sets of operations available:

  1. IEEE compliant stuff, whether sane or not.
  1. Stuff that's fast in hardware, whether sane or not.
  1. Stuff that's sane.

I whole-heartedly agree with this, but, (at most?!) one of these behaviors can be the behavior of the Ord Double instance.

Any proposed change to that instance needs to be made through the library submissions process, so I'm going to close this ticket as "invalid". This discussion should continue, though! Maybe here, or perhaps on a new wiki page—there are getting to be a number of these tickets on floating-point behavior and it would be good to have all the discussion in one place.

comment:15 Changed 5 years ago by lerkok

I hate to beat a dead-horse; but in the light of the similarly filed ticket: https://ghc.haskell.org/trac/ghc/ticket/10378, I thought I'd put in my feedback.

While Carter is right that the being compliant to the IEEE-spec might be useless, it is nonetheless the "spec", and thus we just have to adhere to. I really do want to see Float/Double to mean IEEE-float/double.

The hardware implementation in x86 is actually IEEE compliant; just not in the expected way. The newer ticket I linked to has an implementation that is both IEEE-compliant and follows what x86 hardware is doing. (MINPS, MAXPS instructions.)

I wholeheartedly agree that we need more discussion on all matters relating to floats. In particular (though not relevant in this case), the proper handling of rounding modes, and additional functionality such as fma.

Note: See TracTickets for help on using tickets.