21

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?

Kesong Xie
  • 1,316
  • 3
  • 15
  • 35
  • It works for me. I checked it in playground. But try to convert those values on `Double` type of variable and then check. – Balaji Galave May 11 '17 at 09:19
  • 6
    Then the numbers are probably *not* identical. Try `print(spot.location.latitude.debugDescription)` which prints the number with higher precision. – Martin R May 11 '17 at 09:20
  • 1
    Remove String (value), print directly the value i.e spot.location.latitude & firstSpot.location.latitude and check – Imad Ali May 11 '17 at 09:20
  • @MartinR, it turns out that using `debugDescription`, the output is `32.884218304904714` `32.884218304904707` – Kesong Xie May 11 '17 at 09:22
  • 3
    ... which means that the numbers are different. Problem solved. – Martin R May 11 '17 at 09:23
  • That's not what I expected. Not sure how Swift handles the Double precision. – Kesong Xie May 11 '17 at 09:26
  • 1
    Where do the numbers come from? Why do you expect them to be equal? Are there any calculations involved which might "loose precision"? – Compare also [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) and [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – Martin R May 11 '17 at 09:31
  • First I fetched some geo data from Foursquare api. then I saved it using parse to database. Later on, I just compare it again the new fetching version to the one I stored in the database, probably somewhere in between I somehow lost the precision. – Kesong Xie May 11 '17 at 09:35

9 Answers9

32

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.

Roman Podymov
  • 4,168
  • 4
  • 30
  • 57
Andrea
  • 26,120
  • 10
  • 85
  • 131
8

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.

Community
  • 1
  • 1
Gerriet
  • 1,302
  • 14
  • 20
  • I would rather just let Swift itself handle it, simply using `String(spot.location.latitude) == String(firstSpot.location.latitude)` Thanks. – Kesong Xie May 11 '17 at 09:30
  • 1
    I would not say that *"comparing with == fails"*. It correctly evaluates to false if the numbers are different. – Martin R May 11 '17 at 09:32
  • Kesong Xie: you can surely do that (all depends on your use case), but you will thus decide that locations that are different by less than a millimeter are not the same. – Gerriet May 11 '17 at 09:45
  • Martin R: you are right, I edited my answer to reflect that. – Gerriet May 11 '17 at 16:16
8

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
dimpiax
  • 12,093
  • 5
  • 62
  • 45
6

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;
        }
    }
}
Kirow
  • 1,077
  • 12
  • 25
4

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))
Community
  • 1
  • 1
beshio
  • 794
  • 2
  • 7
  • 17
3

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)
    }
}
Top-Master
  • 7,611
  • 5
  • 39
  • 71
1

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
        }
   }
}

Ankur Lahiry
  • 2,253
  • 1
  • 15
  • 25
LukeSideWalker
  • 7,399
  • 2
  • 37
  • 45
  • 1
    But that is only for fractions, floating precision is also not exact when there are no fractals. Take for example the the Double numbers `let d1: Double = 200000000000000001` and `let d2: Double = 200000000000000000`. `d1 == d2` returns `true`. Your code does ignore that. – Binarian May 06 '19 at 06:56
1

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
Andrey Ezhov
  • 116
  • 1
  • 4
-3

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
Kiran Patil
  • 402
  • 5
  • 13