Skip to content

Fix sNaN behavior of .minimum, .maximum, .minimumMagnitude and .maximumMagnitude. #3361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions stdlib/public/core/FloatingPoint.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -332,40 +332,36 @@ public protocol FloatingPoint: Comparable, IntegerLiteralConvertible,

/// The minimum of `x` and `y`. Implements the IEEE 754 `minNum` operation.
///
/// Returns `x` if `x <= y`, `y` if `y < x`, and whichever of `x` or `y`
/// is a number if the other is NaN. The result is NaN only if both
/// arguments are NaN.
///
/// This function is an implementation hook to be used by the free function
/// min(Self, Self) -> Self so that we get the IEEE 754 behavior with regard
/// to NaNs.
/// If either of `x` or `y` is a signaling NaN, the result is a quiet NaN.
/// Otherwise, the result is `x` if x <= y, `y` if x > y, and whichever
/// is a number if the other is a quiet NaN. If both `x` and `y` are quiet
/// NaNs, a quiet NaN is returned.
static func minimum(_ x: Self, _ y: Self) -> Self

/// The maximum of `x` and `y`. Implements the IEEE 754 `maxNum` operation.
///
/// Returns `x` if `x >= y`, `y` if `y > x`, and whichever of `x` or `y`
/// is a number if the other is NaN. The result is NaN only if both
/// arguments are NaN.
///
/// This function is an implementation hook to be used by the free function
/// max(Self, Self) -> Self so that we get the IEEE 754 behavior with regard
/// to NaNs.
/// If either of `x` or `y` is a signaling NaN, the result is a quiet NaN.
/// Otherwise, the result is `x` if x > y, `y` if x <= y, and whichever
/// is a number if the other is a quiet NaN. If both `x` and `y` are quiet
/// NaNs, a quiet NaN is returned.
static func maximum(_ x: Self, _ y: Self) -> Self

/// Whichever of `x` or `y` has lesser magnitude. Implements the IEEE 754
/// `minNumMag` operation.
///
/// Returns `x` if abs(x) <= abs(y), `y` if abs(y) < abs(x), and whichever of
/// `x` or `y` is a number if the other is NaN. The result is NaN
/// only if both arguments are NaN.
/// If either of `x` or `y` is a signaling NaN, the result is a quiet NaN.
/// Otherwise, the result is `x` if abs(x) <= abs(y), `y` if abs(x) > abs(y),
/// and whichever is a number if the other is a quiet NaN. If both `x` and
/// `y` are quiet NaNs, a quiet NaN is returned.
static func minimumMagnitude(_ x: Self, _ y: Self) -> Self

/// Whichever of `x` or `y` has greater magnitude. Implements the IEEE 754
/// `maxNumMag` operation.
///
/// Returns `x` if abs(x) >= abs(y), `y` if abs(y) > abs(x), and whichever of
/// `x` or `y` is a number if the other is NaN. The result is NaN
/// only if both arguments are NaN.
/// If either of `x` or `y` is a signaling NaN, the result is a quiet NaN.
/// Otherwise, the result is `x` if abs(x) > abs(y), `y` if abs(x) <= abs(y),
/// and whichever is a number if the other is a quiet NaN. If both `x` and
/// `y` are quiet NaNs, a quiet NaN is returned.
static func maximumMagnitude(_ x: Self, _ y: Self) -> Self

/// The least representable value that compares greater than `self`.
Expand Down Expand Up @@ -710,22 +706,38 @@ extension FloatingPoint {
*/

public static func minimum(_ left: Self, _ right: Self) -> Self {
if left.isSignalingNaN || right.isSignalingNaN {
// Produce a quiet NaN matching platform arithmetic behavior.
return left + right
}
if left <= right || right.isNaN { return left }
return right
}

public static func maximum(_ left: Self, _ right: Self) -> Self {
if left >= right || right.isNaN { return left }
if left.isSignalingNaN || right.isSignalingNaN {
// Produce a quiet NaN matching platform arithmetic behavior.
return left + right
}
if left > right || right.isNaN { return left }
return right
}

public static func minimumMagnitude(_ left: Self, _ right: Self) -> Self {
if left.isSignalingNaN || right.isSignalingNaN {
// Produce a quiet NaN matching platform arithmetic behavior.
return left + right
}
if abs(left) <= abs(right) || right.isNaN { return left }
return right
}

public static func maximumMagnitude(_ left: Self, _ right: Self) -> Self {
if abs(left) >= abs(right) || right.isNaN { return left }
if left.isSignalingNaN || right.isSignalingNaN {
// Produce a quiet NaN matching platform arithmetic behavior.
return left + right
}
if abs(left) > abs(right) || right.isNaN { return left }
return right
}

Expand Down
62 changes: 61 additions & 1 deletion test/1_stdlib/FloatingPoint.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,11 @@ FloatingPoint.test("Float80.nextUp, .nextDown")

#endif


extension FloatingPoint {
public var isQuietNaN: Bool { return isNaN && !isSignalingNaN }
}

% for FloatSelf in ['Float32', 'Float64']:

func checkFloatingPointComparison_${FloatSelf}(
Expand Down Expand Up @@ -417,6 +422,7 @@ FloatingPoint.test("${FloatSelf}/{Comparable,Hashable,Equatable}") {
${FloatSelf}.greatestFiniteMagnitude,
${FloatSelf}.infinity,
${FloatSelf}.nan,
${FloatSelf}.signalingNaN,
]
#else
let interestingValues: [${FloatSelf}] = [
Expand All @@ -438,6 +444,7 @@ FloatingPoint.test("${FloatSelf}/{Comparable,Hashable,Equatable}") {
${FloatSelf}.greatestFiniteMagnitude,
${FloatSelf}.infinity,
${FloatSelf}.nan,
${FloatSelf}.signalingNaN,
]
#endif

Expand Down Expand Up @@ -470,6 +477,60 @@ FloatingPoint.test("${FloatSelf}/{Comparable,Hashable,Equatable}") {
ExpectedComparisonResult.gt),
lhs, rhs)
}

// Check minimum, maximum, minimumMagnitude and maximumMagnitude.
let min = ${FloatSelf}.minimum(lhs, rhs)
let max = ${FloatSelf}.maximum(lhs, rhs)
let minMag = ${FloatSelf}.minimumMagnitude(lhs, rhs)
let maxMag = ${FloatSelf}.maximumMagnitude(lhs, rhs)
// If either lhs or rhs is signaling, or if both are quiet NaNs, the
// result is a quiet NaN.
if lhs.isSignalingNaN || rhs.isSignalingNaN || (lhs.isNaN && rhs.isNaN) {
expectTrue(min.isQuietNaN)
expectTrue(max.isQuietNaN)
expectTrue(minMag.isQuietNaN)
expectTrue(maxMag.isQuietNaN)
}
// If only one of lhs and rhs is NaN, the result of the min/max
// operations is always the other value. While contrary to all other
// IEEE 754 basic operations, this property is critical to ensure
// that clamping to a valid range behaves as expected.
else if lhs.isNaN && !rhs.isNaN {
expectEqual(min.bitPattern, rhs.bitPattern)
expectEqual(max.bitPattern, rhs.bitPattern)
expectEqual(minMag.bitPattern, rhs.bitPattern)
expectEqual(maxMag.bitPattern, rhs.bitPattern)
}
else if rhs.isNaN && !lhs.isNaN {
expectEqual(min.bitPattern, lhs.bitPattern)
expectEqual(max.bitPattern, lhs.bitPattern)
expectEqual(minMag.bitPattern, lhs.bitPattern)
expectEqual(maxMag.bitPattern, lhs.bitPattern)
}
// If lhs and rhs are equal, min is lhs and max is rhs. This ensures
// that the set {lhs, rhs} is the same as the set {min, max} so long
// as lhs and rhs are non-NaN. This is less important for min/max than
// it is for minMag and maxMag, but it makes sense to define this way.
if lhs <= rhs {
expectEqual(min.bitPattern, lhs.bitPattern)
expectEqual(max.bitPattern, rhs.bitPattern)
} else if lhs > rhs {
expectEqual(max.bitPattern, lhs.bitPattern)
expectEqual(min.bitPattern, rhs.bitPattern)
}
// If lhs and rhs have equal magnitude, minMag is lhs and maxMag is rhs.
// This ensures that the set {lhs, rhs} is the same as the set
// {minMag, maxMag} so long as lhs and rhs are non-NaN; this is a
// restriction of the IEEE 754 rules, but it makes good sense and also
// makes this primitive more useful for building head-tail arithmetic
// operations.
if abs(lhs) <= abs(rhs) {
expectEqual(minMag.bitPattern, lhs.bitPattern)
expectEqual(maxMag.bitPattern, rhs.bitPattern)
} else if abs(lhs) > abs(rhs) {
expectEqual(maxMag.bitPattern, lhs.bitPattern)
expectEqual(minMag.bitPattern, rhs.bitPattern)
}
}
}
}
Expand Down Expand Up @@ -1022,4 +1083,3 @@ FloatingPointClassification.test("FloatingPointClassification/Equatable") {
}

runAllTests()