Skip to content

Float min/max/clamp follow IEEE754-2008 (and not IEEE754-2019) WRT negative zeros #83984

@thomcc

Description

In a blog post (shameless plug) I wrote on clamping between 0.0 and 1.0, I noticed that we don't treat -0.0 as less than +0.0. Instead, we return whichever is the first argument:

>> f32::min(0.0, -0.0)
0.0
>> f32::min(-0.0, 0.0)
-0.0
>> f32::max(0.0, -0.0)
0.0
>> f32::max(-0.0, 0.0)
-0.0

That behavior is kinda incoherent, probably unintentional, and seems highly unlikely that anybody is relying on it, so I suspect we're free to fix it.

While IEEE-754 defines two families of min/max functions (differing on NaN handling), all of them are required to treat -0.0 as less than +0.0. We should fix our version to do this ¹.

This problem is present in {float}::clamp too, which ideally would produce +0.0 for f32::clamp(-0.0, +0.0, _). (However, note that clamp uses a different² version of min/max than max/min, so fixing it here won't automatically fix it for clamp).

Excerpt from IEEE754 2019 section 9.6 "Minimum and maximum operations" (emphasis mine)

  • maximum(x, y) is x if x > y, y if y > x and a quiet NaN if either operand is a NaN, according to 6.2. For this operation, +0 compares greater than −0. ...

  • maximumNumber(x, y) is x if x > y, y if y > x, and the number if one operand is a number and the other is a NaN. For this operation, +0 compares greater than −0. ...

  • minimum(x, y) is x if x < y, y if y < x, and a quiet NaN if either operand is a NaN, according to 6.2. For this operation, −0 compares less than +0. ...

  • minimumNumber(x, y) is x if x < y, y if y < x, and the number if one operand is a number and the other is a NaN. For this operation, −0 compares less than +0. ...

CC @workingjubilee


¹ Alternatively, since these functions only "should" be provided, we could say that we just don't provide equivalents to those functions and that ours are different functions. If we do this, we should have a very good reason IMO, and should document this as well.

² Note that for reasons³ our {float}::clamp functions are documented as propagating NaNs, but {float}::{min, max} are not. This means this does have to be fixed in multiple places, although, it's coherent under IEEE754 if we assume

  • {float}::{min, max} use minimumNumber and maximumNumber respectively
  • {float}::clamp uses minimum and maximum

³ Nobody asked, but personally, I'd rather clamp be consistent with min/max, which for this has the benefit of guaranteeing that the result is always in the provided bound, which is quite desirable sometimes. I know it was probably deliberate, and I do get why, and know it can't be changed, but I'm still going to use this footnote-within-a-footnote on an obscure floating point bug report to gripe about it.

Metadata

Assignees

No one assigned

    Labels

    A-floating-pointArea: Floating point numbers and arithmeticC-bugCategory: This is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions