Kesong Xie
Kesong Xie

Reputation: 1396

Comparing two identical Double value return false Swift 3

I tried to compare two identical latitude, which is of type Double, and when I print the result, it's evaluated as false.

print("spot latitude: " + String(spot.location.latitude))
print("first object: " + String(firstSpot.location.latitude))  
print(spot.location.latitude == firstSpot.location.latitude)

Output:

spot latitude: 32.8842183049047
first object: 32.8842183049047
false

Anyone has any idea what's going on?

Upvotes: 25

Views: 24666

Answers (9)

Top-Master
Top-Master

Reputation: 8745

Merging different Swift 4 technics together:

import Foundation

infix operator ~==
infix operator ~<=
infix operator ~>=

extension Double {
    static func ~== (lhs: Double, rhs: Double) -> Bool {
        // If we add even a single zero more,
        // our Decimal test would fail.
        fabs(lhs - rhs) < 0.000000000001
    }

    static func ~<= (lhs: Double, rhs: Double) -> Bool {
        (lhs < rhs) || (lhs ~== rhs)
    }

    static func ~>= (lhs: Double, rhs: Double) -> Bool {
        (lhs > rhs) || (lhs ~== rhs)
    }
}

Usage:

// Check if two double variables are almost equal:
if a ~== b {
    print("was nearly equal!")
}

Note that ~<= is the fuzzy-compare version of less-than (<=) operator,
And ~>= is the fuzzy-compare greater-than (>=) operator.

Also, originally I was using Double.ulpOfOne, but changed to a constant (to be even more fuzzy).

Finally, as mentioned on my profile, usage under Apache 2.0 license is allowed as well (without attribution need).

Testing

import Foundation
import XCTest
@testable import MyApp

class MathTest: XCTestCase {
    func testFuzzyCompare_isConstantBigEnough() {
        // Dummy.
        let decimal: Decimal = 3062.36
        let double: Double = 3062.36
        // With casting up from low-precession type.
        let doubleCasted: Double = NSDecimalNumber(decimal: decimal).doubleValue

        // Actual test.
        XCTAssertEqual(decimal, Decimal(doubleCasted));
        XCTAssertNotEqual(double, doubleCasted);
        XCTAssertTrue(double ~== doubleCasted);
    }

    func testDouble_correctConstant() {
        XCTAssertEqual(Double.ulpOfOne, 2.220446049250313e-16)
    }
}

Upvotes: 3

Andrea
Andrea

Reputation: 26385

Comparing equality in doubles rarely gave you the expected answer this is due to how doubles are stored. You can create custom operators, keeping in mind that you should work with sort of accuracy.
To know more you can check this answer, even if it speaks about ObjC the principles are super valid.
Since I had the same problem checking online I've found this answer on the apple dev forum.
This function should do the trick, you can easily create a custom operator:

func doubleEqual(_ a: Double, _ b: Double) -> Bool {
    return fabs(a - b) < Double.ulpOfOne
}

I've tried to convert from swift 2.x to 3.x it seems that the macro DBL_EPSILON is not available anymore.

Upvotes: 36

Andrey Ezhov
Andrey Ezhov

Reputation: 116

One-liner with flexible precision. Works well for money

extension Float {
  func compare(with number: Float, precision: Int = 2) -> Bool {
    abs(self.distance(to: number)) < powf(10, Float(-precision))
  }
}

Example

let float1: Float = 0.1
let float2: Float = 0.2

(float1 * float2).compare(with: 0.02) // TRUE
(float1 * float2).compare(with: 0.02, precision: 1) // TRUE
(float1 * float2).compare(with: 0.02, precision: 3) // TRUE
Float(0.021).compare(with: 0.02) // TRUE
Float(0.021).compare(with: 0.02, precision: 3) // FALSE
(float1 * float2).isEqual(to: 0.02) // FALSE

Upvotes: 1

Kiran Patil
Kiran Patil

Reputation: 432

Please follow the below-updated code

    var a = -21.5
    var b = 305.15

   if(a.isEqual(to: b)){
     print(a)
   } else {
     print(b)
   }

Output

//prints 305.15

Upvotes: -3

Kirow
Kirow

Reputation: 1210

You can use this extension

extension FloatingPoint {
    func isNearlyEqual(to value: Self) -> Bool {
        return abs(self - value) <= .ulpOfOne
    }
}

or according to this guide

extension FloatingPoint {
    func isNearlyEqual(to value: Self) -> Bool {
        let absA = abs(self)
        let absB = abs(value);
        let diff = abs(self - value);

        if self == value { // shortcut, handles infinities
            return true
        } else if self == .zero || value == .zero || (absA + absB) < Self.leastNormalMagnitude {
            // a or b is zero or both are extremely close to it
            // relative error is less meaningful here
            return diff < Self.ulpOfOne * Self.leastNormalMagnitude
        } else { // use relative error
            return diff / min((absA + absB), Self.greatestFiniteMagnitude) < .ulpOfOne;
        }
    }
}

Upvotes: 7

dimpiax
dimpiax

Reputation: 12677

Swift 5, 4

Probably solution with rounding is best choice to get close values:

extension Double {
  static func equal(_ lhs: Double, _ rhs: Double, precise value: Int? = nil) -> Bool {
    guard let value = value else {
      return lhs == rhs
    }
        
    return lhs.precised(value) == rhs.precised(value)
  }

  func precised(_ value: Int = 1) -> Double {
    let offset = pow(10, Double(value))
    return (self * offset).rounded() / offset
  }
}

// values retrieving
a: 64.3465535142464, b: 64.3465535142464
    
// values debug description
a: 64.346553514246409, b: 64.346553514246395

// calculations
a == b // false
a.precised(10) == b.precised(10) // true
// or
Double.equal(a, b) // false
Double.equal(a, b, precise: 10) // true

If using amendment with epsilon, anyway I get false equal with doubles:

// values retrieving
a: 64.3465535142464, b: 64.3465535142464

// values debug description
a: 64.346553514246409, b: 64.346553514246395

// calculations
a == b // false
a - b  // 1.4210854715202e-14
a - b < .ulpOfOne // false

Upvotes: 8

LukeSideWalker
LukeSideWalker

Reputation: 7900

Swift 4.1:

I found a solution, that works pretty well for me. It is flexible, because you can define the "precision" for the comparing by passing the number of fractional digits that should be regarded and made it an extension of Double.

Lets say we have two values for example:

  • value01 = 5.001 and
  • value02 = 5.00100004

These values are considerd "equal" when only 3 fractional digits are regarded, but regarded es "not equal" regarding 8 fractional digits.

extension Double {

    /// Compares the receiver (Double) with annother Double considering a defined
    /// number of fractional digits.

    func checkIsEqual(toDouble pDouble : Double, includingNumberOfFractionalDigits : Int) -> Bool {

        let denominator         : Double = pow(10.0, Double(includingNumberOfFractionalDigits))
        let maximumDifference   : Double = 1.0 / denominator
        let realDifference      : Double = fabs(self - pDouble)

        if realDifference >= maximumDifference {
            return false
        } else {
            return true
        }
   }
}

Upvotes: 1

Gerriet
Gerriet

Reputation: 1350

Comparing double or float values with == will not give the expected result in most programming languages, which means that numbers that you think should be equal are in fact slightly different. Instead compute the absolute difference and handle the numbers as equal if the difference is below some threshold. See Compare double to zero using epsilon for more explanations.

Upvotes: 10

beshio
beshio

Reputation: 804

I think because your two Double numbers are actually different. When you convert Double to String in decimal number, rounding can't be avoided due to floating point representation. Even if two Strings look the same, actual numbers can differ. See this for more detail.

You can examine two numbers further by specifying more digits with:

print(String(format: "%.20f", double_number))

Upvotes: 4

Related Questions