1

Yes, I've done my research, and the closest page I could find to my question is: this page and it is not in the least helpful.

That said, is it safe (reliable) to use floating-point values as keys in a Swift Dictionary ? Specifically, CGFloat, which the Apple docs say is actually a Double on 64-bit systems.

P.S. (This is important) - My CGFloat values will always end with .0 or .5 as fractional parts. Examples - 13, 13.5, 14, 14.5, etc. Will this make a difference to the reliability of such Dictionary lookups ?

var dictionary: [CGFloat: MyObject] = [:]

...

// Could this type of lookup fail ?
if let mappedObject = dictionary[13.5] {
   ...
}

I have a unique use case that requires mapping certain objects to these CGFloat values for quick lookups later.

I know from my experience in unit testing, that XCTAssertEqual sometimes fails when comparing two floating-point types, without the use of an "accuracy" parameter. So, I'm afraid that using CGFloats as keys will cause failed subsequent lookups as the keys passed in may be off by 0.0000000001 or something like that, because of the underlying representation of floating-point types.

Thank you.


EDIT - (Thanks @Sweeper) Yes, I did think of using Int keys by converting the CGFloat to Int using the following conversion:

intKey = Int(lroundf(Float(cgFloatKey * 2.0))

But then, I'm concerned about this ^ conversion (which has to be done on each lookup) requiring enough overhead to make it not worth it.

I might be totally wrong here and maybe these operations are lightning-fast. Can anyone comment on the efficiency of such an operation every single time a lookup is performed ?

waldenCalms
  • 100
  • 1
  • 8
  • 3
    If your floats all end with .0 or .5, then why not just store them as an integer number of halves? – Sweeper Jul 19 '21 at 06:08
  • @Sweeper - Thank you. Yes, I had thought of that, but then I was afraid that the overhead of the operation to convert the CGFloat to Int each time would result in losing the benefit of the quick lookups later. Any thoughts on the efficiency of the CGFloat -> Int operation ? – waldenCalms Jul 19 '21 at 06:20

2 Answers2

2

Your concerns are justified, you know XCTAssertEqual may fail to give the expected result due to how floating-point values work and using floating-point values as keys will therefore have the same issues as key lookup relies on equality.

Think of another way, like using a suitable integer value as the key.

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Thank you, this is the reassurance I needed. Another person also suggested using Int keys. I think I will do that instead. Cheers ! Any thoughts on the efficiency of a CGFloat -> Int operation such as rounding ? – waldenCalms Jul 19 '21 at 06:17
  • 1
    As a general statement you don't need to concern yourself with the cost/efficiency of rounding or similar operations. You've already found `lroundf()`, but you might want to consider just using `lround()` – a `CGFloat` is a `double`and you can also use `Int` as a key so `intKey = lround(Double(cgFloatKey) * 2)` will compute your ½ count allowing for ridiculously large accumulated errors due to floating-point math and the `Double` cast is free. Or you could just keep the values as integral ½ counts and format as "floats" when needed – depends on what you're doing with the values. – CRD Jul 19 '21 at 19:42
  • Great, thanks. My values always start out as CGFloat, so I will have to do a conversion from CGFloat -> Int on every single dictionary lookup. Hence my question about efficiency. It is probably a negligible computational cost, but I just wanted to check with someone more knowledgeable than me :) – waldenCalms Jul 19 '21 at 20:36
2

XCTAssertEqual does not fail when comparing floating-point data. It generates a failure if and only if the arguments are not equal, which is correct behavior.

If the arguments are not equal but would have been if computed with exact mathematics, this is because there was a rounding error in some prior operation, not in XCTAssertEqual. The concern about floating-point numbers is that arithmetic operations and mathematical functions on them are approximations to real arithmetic, and therefore computed results may differ from the ideal results desired from exact mathematics. Thus, one cannot use XCTAssertEqual to ensure that two results computed with approximations are equal when they in fact might not be equal due to the approximations. The reason for this lies entirely in the arithmetic approximations, not in the behavior of XCTAssertEqual.

If the fractional parts of your values are always 0 or ½, then these concerns do not apply (unless your numbers may be off by large amounts, such as producing 13½ instead of 13). So, if your numbers are always the exact values you want, concerns about arithmetic approximations do not apply, and they will be suitable for use as dictionary keys.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Thanks for the answer. So if I understand you correctly, approximation errors only occur when a value is manipulated through an operation such as rounding (or addition / multiplication, etc), but if they are just used as constants, with no arithmetic performed, then they can be used safely as Dictionary keys ? – waldenCalms Jul 19 '21 at 19:26
  • 1
    I'd say *probably* – Eric is pointing out that the decimal fraction 0.5 has an exact binary fraction representation (0.1). So *provided* you avoid all math and the value isn't created as 0.49999.... in the first place (unlikely) then you *should* be OK. Or you could code defensively so a future simple change doesn't introduce a bug. – CRD Jul 19 '21 at 19:50
  • 1
    @waldenCalms: Yes. The IEEE-754 standard is clear: Floating-point data does not approximate real numbers. Each floating-point datum that is not a NaN represents one number exactly, not an approximation. Approximations occur only in operations: Basic floating-point operations produce the result as if computed with perfect real-number arithmetic and then rounded to the nearest representable value (in a direction determined by a rounding rule, most often to the nearest representable value). If you do not use those operations, there are no errors. – Eric Postpischil Jul 19 '21 at 20:05
  • 1
    @waldenCalms: Note that converting from some external character sequence, such as a decimal numeral in a string, to a floating-point value is an operation that will round its results. But, given the numbers you are working with, that should not be an issue, unless you use extremely large numbers in the range where not all integers are representable (above 2^53). – Eric Postpischil Jul 19 '21 at 20:25
  • Great, thanks Eric and CRD ! This is all good to know. – waldenCalms Jul 19 '21 at 20:32