-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from RougeWare/feature/Equals-with-tolerance
Protocol-driven equality with tolerance
- Loading branch information
Showing
7 changed files
with
213 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,33 @@ | ||
# Swift Basic Math Tools # | ||
|
||
Some basic tools for doing math in Swift | ||
|
||
|
||
|
||
# `TolerablyEqual` # | ||
|
||
This is a protocol that is applied to all the language's built-in signed numbers, but which can be applied to anything, which lets you compare two values for equality, within a certain tolerance. | ||
|
||
Let's look at this classic example: | ||
```swift | ||
let shouldBeOne = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 | ||
|
||
print(shouldBeOne, shouldBeOne == 1) | ||
// prints 0.9999999999999999 false | ||
``` | ||
|
||
|
||
With this package, you can use the `~==` operator to easily overcome this problem | ||
```swift | ||
print(shouldBeOne ~== 1) | ||
// prints true | ||
``` | ||
|
||
|
||
You can also customize this behavior as needed: | ||
```swift | ||
print(1_000_000.equals(1_000_100, tolerance: 1_000)) | ||
// prints true | ||
``` | ||
|
||
Feel free to check out the tests for more examples! |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// | ||
// Equality with tolerance.swift | ||
// | ||
// | ||
// Created by Ben Leggiero on 2020-08-02. | ||
// | ||
|
||
import Foundation | ||
import CoreGraphics.CGBase | ||
|
||
|
||
|
||
/// A type which can have its equality compared within a certain tolerance | ||
public protocol TolerablyEqual { | ||
|
||
/// Determines whether this number is equal to the other one, within the given tolerance. | ||
/// | ||
/// For example: | ||
/// ```swift | ||
/// 123.456.equals(123.457, tolerance: 0.01) == true | ||
/// 123.456.equals(123.457, tolerance: 0.001) == true | ||
/// 123.456.equals(123.458, tolerance: 0.001) == false | ||
/// 1_000_000.equals(1_000_100, tolerance: 1_000) == true | ||
/// 1_000_000.equals(1_100_000, tolerance: 1_000) == false | ||
/// ``` | ||
/// | ||
/// | ||
/// - Parameters: | ||
/// - other: The other number to compare to this one | ||
/// - tolerance: The maximum distance which `other` is allowed to be away from `self` | ||
/// | ||
/// - Returns: `true` iff `self` and `other` are, at most `tolerance` apart from each other | ||
func equals(_ other: Self, tolerance: Self) -> Bool | ||
|
||
|
||
/// The tolerance in equality-comparisons used when none is specified. Setting this will determine how all future | ||
/// tolerably-equal checks behave. | ||
static var defaultTolerance: Self { get set } | ||
|
||
|
||
/// Determines whether the left umber is equal to the right, within the default tolderance. | ||
/// | ||
/// To read and change the default tolerance, use the static `defaultTolerance` variable. | ||
/// | ||
/// - Parameters: | ||
/// - lhs: The first value to compare | ||
/// - rhs: The value to compare to the first value, within the default tolerance | ||
static func ~==(lhs: Self, rhs: Self) -> Bool | ||
} | ||
|
||
|
||
|
||
// MARK: - Operator | ||
|
||
/// Indicates approximate equality. For example, `1.0 ~== 1.000000001` | ||
infix operator ~== : ComparisonPrecedence | ||
|
||
|
||
|
||
// MARK: Synthesis | ||
|
||
public extension TolerablyEqual { | ||
static func ~==(lhs: Self, rhs: Self) -> Bool { | ||
lhs.equals(rhs, tolerance: defaultTolerance) | ||
} | ||
} | ||
|
||
|
||
|
||
public extension TolerablyEqual | ||
where Self: SignedNumeric, | ||
Self: Comparable | ||
{ | ||
func equals(_ other: Self, tolerance: Self) -> Bool { | ||
return abs(self - other) <= tolerance | ||
} | ||
} | ||
|
||
|
||
|
||
// MARK: Default Conformances | ||
|
||
extension CGFloat: TolerablyEqual { public static var defaultTolerance = CGFloat(CGFloat.NativeType.defaultTolerance) } | ||
extension Float32: TolerablyEqual { public static var defaultTolerance: Float32 = 0.0001 } | ||
extension Float64: TolerablyEqual { public static var defaultTolerance: Float64 = 0.00001 } | ||
extension Float80: TolerablyEqual { public static var defaultTolerance: Float80 = 0.000001 } | ||
|
||
extension Int: TolerablyEqual { public static var defaultTolerance: Int = 0 } | ||
extension Int8: TolerablyEqual { public static var defaultTolerance: Int8 = 0 } | ||
extension Int16: TolerablyEqual { public static var defaultTolerance: Int16 = 0 } | ||
extension Int32: TolerablyEqual { public static var defaultTolerance: Int32 = 0 } | ||
extension Int64: TolerablyEqual { public static var defaultTolerance: Int64 = 0 } |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// | ||
// TolerablyEqual Tests.swift | ||
// | ||
// | ||
// Created by Ben Leggiero on 2020-08-02. | ||
// | ||
|
||
import XCTest | ||
import BasicMathTools | ||
|
||
|
||
|
||
private let originalFloat64DefaultTolerance = Float64.defaultTolerance | ||
|
||
|
||
|
||
final class TolerablyEqual_Tests: XCTestCase { | ||
|
||
override func setUp() { | ||
Float64.defaultTolerance = originalFloat64DefaultTolerance | ||
} | ||
|
||
|
||
func testEqualsWithTolerance() { | ||
XCTAssertTrue(123.456.equals(123.457, tolerance: 0.01)) | ||
XCTAssertTrue(123.456.equals(123.457, tolerance: 0.001)) | ||
XCTAssertFalse(123.456.equals(123.458, tolerance: 0.001)) | ||
XCTAssertTrue(1_000_000.equals(1_000_100, tolerance: 1_000)) | ||
XCTAssertFalse(1_000_000.equals(1_100_000, tolerance: 1_000)) | ||
} | ||
|
||
|
||
func testTildeDoubleEquals_Float64() { | ||
|
||
XCTAssertFalse(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1) | ||
XCTAssertTrue (0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 ~== 1) | ||
|
||
XCTAssertFalse(2 == sqrt(2) * sqrt(2)) | ||
XCTAssertTrue (2 ~== sqrt(2) * sqrt(2)) | ||
|
||
XCTAssertTrue(5 ~== 4.9999999) | ||
XCTAssertTrue(5 ~== 5) | ||
XCTAssertTrue(5 ~== 5.0000001) | ||
} | ||
|
||
|
||
func testDefaultTolerance_Float64() { | ||
|
||
XCTAssertEqual(Float64.defaultTolerance, 0.00001) | ||
|
||
XCTAssertFalse(5 ~== 4) | ||
XCTAssertFalse(5 ~== 4.9) | ||
XCTAssertFalse(5 ~== 4.99) | ||
XCTAssertFalse(5 ~== 4.999) | ||
XCTAssertFalse(5 ~== 4.9999) | ||
XCTAssertTrue(5 ~== 4.99999) | ||
XCTAssertTrue(5 ~== 4.999999) | ||
XCTAssertTrue(5 ~== 5) | ||
XCTAssertTrue(5 ~== 5.000001) | ||
XCTAssertTrue(5 ~== 5.00001) | ||
XCTAssertFalse(5 ~== 5.0001) | ||
XCTAssertFalse(5 ~== 5.001) | ||
XCTAssertFalse(5 ~== 5.01) | ||
XCTAssertFalse(5 ~== 5.1) | ||
XCTAssertFalse(5 ~== 6) | ||
|
||
XCTAssertFalse(5 ~== 4.99998) | ||
XCTAssertFalse(5 ~== 5.00002) | ||
|
||
Float64.defaultTolerance = 0.1 | ||
|
||
XCTAssertEqual(Float64.defaultTolerance, 0.1) | ||
XCTAssertTrue(5 ~== 5.0000001) | ||
XCTAssertTrue(5 ~== 5.000001) | ||
XCTAssertTrue(5 ~== 5.00001) | ||
XCTAssertTrue(5 ~== 5.0001) | ||
XCTAssertTrue(5 ~== 5.001) | ||
XCTAssertTrue(5 ~== 5.01) | ||
XCTAssertTrue(5 ~== 5.1) | ||
XCTAssertFalse(5 ~== 6) | ||
} | ||
|
||
|
||
static var allTests = [ | ||
("testEqualsWithTolerance", testEqualsWithTolerance), | ||
("testTildeDoubleEquals_Float64", testTildeDoubleEquals_Float64), | ||
("testDefaultTolerance_Float64", testDefaultTolerance_Float64), | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters