RosettaCodeData/Task/Approximate-equality/Swift/approximate-equality.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))")
}