FloatingPoint equality ..


(Gavin Eadie) #1

I've spent a fascinating evening and morning in the arcane depths of
floating point, specifically researching the comparison of two floating
point numbers. I pretty much understand how to do this with a combination
of 'epsilon' and 'ULPs' after reading this
<https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/>
.

For example, for a off-by-one ULP comparison:

public func almostEqual(_ a: Double, _ b: Double) -> Bool {

    return a == b ||

           a == nextafter(b, +.greatestFiniteMagnitude) ||

           a == nextafter(b, -.greatestFiniteMagnitude)

}

My question is whether Swift has a built in method that provides an 'almost
equal' comparison?

Or, asking the same question another way, what doesn't the Swift method

     func isEqual(to other: Self) -> Bool
<https://developer.apple.com/documentation/swift/bool>

actually do? Does it test for equality of the binary representation of
'self' and 'other' (I assume it must given no 'precision' argument) .. I
read it follows the IEEE meaning of equality but that document is not on my
bookshelf and is quite expensive!

Apologies if this has been asked and answered before .. Gavin


(Stephen Canon) #2

I've spent a fascinating evening and morning in the arcane depths of floating point, specifically researching the comparison of two floating point numbers. I pretty much understand how to do this with a combination of 'epsilon' and 'ULPs' after reading this <https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/>.

For example, for a off-by-one ULP comparison:

public func almostEqual(_ a: Double, _ b: Double) -> Bool {
    return a == b ||
           a == nextafter(b, +.greatestFiniteMagnitude) ||
           a == nextafter(b, -.greatestFiniteMagnitude)
}

My question is whether Swift has a built in method that provides an 'almost equal' comparison?

It does not. It is “trivial” to implement such a comparison (just like you do here); the difficulty is entirely in guiding users in choosing a means of comparison and tolerance appropriate to their problem.

Or, asking the same question another way, what doesn't the Swift method

     func isEqual(to other: Self) -> Bool <https://developer.apple.com/documentation/swift/bool>

actually do? Does it test for equality of the binary representation of 'self' and 'other' (I assume it must given no 'precision' argument) .. I read it follows the IEEE meaning of equality but that document is not on my bookshelf and is quite expensive!

isEqual implements IEEE equality, which is *not the same* as bitwise equality of representation. In particular:

1. x != x is true when x is NaN.
2. –0 == +0, but the two values have different encodings.
3. Float80 admits non-canonical “pseudo denormal” values that compare equal to a canonical value with a different encoding.
4. IEEE 754 Decimal types (not implemented in the standard library) have *many* encodings that compare equal.

– Steve

···

On Jun 29, 2017, at 2:40 PM, Gavin Eadie via swift-users <swift-users@swift.org> wrote:


(Stephen Canon) #3

I should also point out:

(a) your code can be somewhat simpler in Swift. I would probably write something along the lines of:

func almostEqual<T: FloatingPoint>(_ a: T, _ b: T) -> Bool {
    return a >= b.nextDown && a <= b.nextUp
}

(b) one ULP is almost never a tolerance you want to use. It’s much too small for almost all computations, and too large for most of the remaining ones.

– Steve


(^) #4

You can make it even simpler with a range pattern

func almost_equal<T:FloatingPoint>(_ a:T, _ b:T) -> Bool
{
    return b.nextDown ... b.nextUp ~= a
}

···

On Thu, Jun 29, 2017 at 3:05 PM, Stephen Canon via swift-users < swift-users@swift.org> wrote:

I should also point out:

(a) your code can be somewhat simpler in Swift. I would probably write
something along the lines of:

func almostEqual<T: FloatingPoint>(_ a: T, _ b: T) -> Bool {
    return a >= b.nextDown && a <= b.nextUp
}

(b) one ULP is almost never a tolerance you want to use. It’s much too
small for almost all computations, and too large for most of the remaining
ones.

– Steve

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Gavin Eadie) #5

.. agreed but this looks too awful (and is mostly a joke!)

    return a >= b.nextDown.nextDown.nextDown.nextDown && a <= b.nextUp.
nextUp.nextUp.nextUp

Thanks, friends, for your insights and info .. Gavin

···

On Thu, Jun 29, 2017 at 3:30 PM, Taylor Swift via swift-users < swift-users@swift.org> wrote:

(b) one ULP is almost never a tolerance you want to use. It’s much too
small for almost all computations, and too large for most of the remaining
ones.

– Steve

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(David Sweeris) #6

I'd probably do this:
precedencegroup FauxTwoPartOperatorPrecedence {
  associativity: right
  higherThan: BitwiseShiftPrecedence
}
public struct VaE<T> {
  var value: T
  var epsilon: T
}
infix operator ± : FauxTwoPartOperatorPrecedence // `±` is typed "shift-opt-=", at least with macOS's default QWERTY US keyboard layout
public func ± <T: BinaryFloatingPoint> (value: T, epsilon: T) -> VaE<T> {
  return VaE(value: value, epsilon: epsilon)
}
public func == <T: BinaryFloatingPoint> (lhs: T, rhs: VaE<T>) -> Bool {
  return lhs <= (rhs.value + rhs.epsilon) && lhs >= (rhs.value - rhs.epsilon)
}

0.0 == 0.0 ± 0.1 // true
1.0 == 0.0 ± 0.1 // false
-0.3 == 0.0 ± 0.5 // true

(or use something like `+-`, if you prefer your custom operators to be not quite that custom)

Hope that helps,
- Dave Sweeris

···

On Jun 29, 2017, at 2:20 PM, Gavin Eadie via swift-users <swift-users@swift.org> wrote:

.. agreed but this looks too awful (and is mostly a joke!)

    return a >= b.nextDown.nextDown.nextDown.nextDown && a <= b.nextUp.nextUp.nextUp.nextUp

Thanks, friends, for your insights and info .. Gavin

On Thu, Jun 29, 2017 at 3:30 PM, Taylor Swift via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

(b) one ULP is almost never a tolerance you want to use. It’s much too small for almost all computations, and too large for most of the remaining ones.

– Steve

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users