A correct way to determine if two floating-point numbers are approximately equal to one another in Swift.
Swift
Switch branches/tags
Nothing to show
Clone or download
Latest commit 252be81 Jul 3, 2018

README.md

FloatingPointApproximation

A correct way to determine if two floating-point numbers are approximately equal to one another.

This functionality is discussed in Chapter 3 of Flight School Guide to Swift Numbers.

Requirements

  • Swift 4.0+

Installation

Swift Package Manager

Add the FloatingPointApproximation package to your target dependencies in Package.swift:

import PackageDescription

let package = Package(
  name: "YourProject",
  dependencies: [
    .package(
        url: "https://github.com/Flight-School/FloatingPointApproximation",
        from: "1.0.0"
    ),
  ]
)

Then run the swift build command to build your project.

Carthage

To use FloatingPointApproximation in your Xcode project using Carthage, specify it in Cartfile:

github "Flight-School/FloatingPointApproximation" ~> 1.0.0

Then run the carthage update command to build the framework, and drag the built FloatingPointApproximation.framework into your Xcode project.

Usage

Floating-point arithmetic can produce unexpected results, such as 0.1 + 0.2 != 0.3. The reason for this is that many fractional numbers, including 0.1, 0.2, and 0.3, cannot be precisely expressed in a binary number representation.

A common mistake is to use an arbitrarily small constant (such as .ulpOfOne) to determine whether two floating-point numbers are approximately equal. For example:

let actual = 0.1 + 0.2
let expected = 0.3
abs(expected - actual) < .ulpOfOne // true

However, this doesn't work for large scale numbers:

let actual = 1e25 + 2e25
let expected = 3e25
abs(expected - actual) < .ulpOfOne // false

A better approach for determining approximate equality would be to count how many representable values, or ULPs, exist between two floating-point numbers.

The ==~ operator (and its complement, !=~) defined by this package returns a Boolean value indicating whether two floating-point numbers are approximately equal.

import FloatingPointApproximation

0.1 + 0.2 == 0.3 // false
0.1 + 0.2 ==~ 0.3 // true

Floating-point numbers are defined to be approximately equal if they are within one unit of least precision, or ULP, of one another.

Because of how Swift implements floating-point numbers, the implementation of the ==~ operator is quite simple:

func ==~<T> (lhs: T, rhs: T) -> Bool where T: FloatingPoint  {
    return lhs == rhs || lhs.nextDown == rhs || lhs.nextUp == rhs
}

A more complete approach combines both absolute and relative comparisons. The isApproximatelyEqual(to:within:maximumULPs:) method determines whether a floating-point number is approximately equal to another value by first checking to see if it is within a given absolute margin, if provided, and then checking to see if it falls within a given number of ULPs:

import FloatingPointApproximation

(0.1 + 0.2).isApproximatelyEqual(to: 0.3, within: 1e-12, maximumULPs: 2)

Ultimately, it's your responsibility to determine how to compare two floating-point numbers for approximate equality based on the requirements of your domain.

License

MIT

Contact

Mattt (@mattt)