69 lines
2.5 KiB
Swift
69 lines
2.5 KiB
Swift
import Foundation
|
|
|
|
extension FloatingPoint {
|
|
@inlinable
|
|
public func isAlmostEqual(
|
|
to other: Self,
|
|
tolerance: Self = Self.ulpOfOne.squareRoot()
|
|
) -> Bool {
|
|
// tolerances outside of [.ulpOfOne,1) yield well-defined but useless results,
|
|
// so this is enforced by an assert rathern than a precondition.
|
|
assert(tolerance >= .ulpOfOne && tolerance < 1, "tolerance should be in [.ulpOfOne, 1).")
|
|
|
|
// The simple computation below does not necessarily give sensible
|
|
// results if one of self or other is infinite; we need to rescale
|
|
// the computation in that case.
|
|
guard self.isFinite && other.isFinite else {
|
|
return rescaledAlmostEqual(to: other, tolerance: tolerance)
|
|
}
|
|
|
|
// This should eventually be rewritten to use a scaling facility to be
|
|
// defined on FloatingPoint suitable for hypot and scaled sums, but the
|
|
// following is good enough to be useful for now.
|
|
let scale = max(abs(self), abs(other), .leastNormalMagnitude)
|
|
return abs(self - other) < scale*tolerance
|
|
}
|
|
|
|
@usableFromInline
|
|
internal func rescaledAlmostEqual(to other: Self, tolerance: Self) -> Bool {
|
|
// NaN is considered to be not approximately equal to anything, not even
|
|
// itself.
|
|
if self.isNaN || other.isNaN { return false }
|
|
if self.isInfinite {
|
|
if other.isInfinite { return self == other }
|
|
|
|
// Self is infinite and other is finite. Replace self with the binade
|
|
// of the greatestFiniteMagnitude, and reduce the exponent of other by
|
|
// one to compensate.
|
|
let scaledSelf = Self(sign: self.sign,
|
|
exponent: Self.greatestFiniteMagnitude.exponent,
|
|
significand: 1)
|
|
let scaledOther = Self(sign: .plus,
|
|
exponent: -1,
|
|
significand: other)
|
|
|
|
// Now both values are finite, so re-run the naive comparison.
|
|
return scaledSelf.isAlmostEqual(to: scaledOther, tolerance: tolerance)
|
|
}
|
|
|
|
// If self is finite and other is infinite, flip order and use scaling
|
|
// defined above, since this relation is symmetric.
|
|
return other.rescaledAlmostEqual(to: self, tolerance: tolerance)
|
|
}
|
|
}
|
|
|
|
let testCases = [
|
|
(100000000000000.01, 100000000000000.011),
|
|
(100.01, 100.011),
|
|
(10000000000000.001 / 10000.0, 1000000000.0000001000),
|
|
(0.001, 0.0010000001),
|
|
(0.000000000000000000000101, 0.0),
|
|
(sqrt(2) * sqrt(2), 2.0),
|
|
(-sqrt(2) * sqrt(2), -2.0),
|
|
(3.14159265358979323846, 3.14159265358979324)
|
|
]
|
|
|
|
for testCase in testCases {
|
|
print("\(testCase.0), \(testCase.1) => \(testCase.0.isAlmostEqual(to: testCase.1))")
|
|
}
|