2

Using stride with Swift 4 sometimes results in wrong results, what is demonstrated by this piece of code:

import UIKit
import PlaygroundSupport
struct Demo {
    let omegaMax = 10.0
    let omegaC = 1.0
    var omegaSignal:Double {get {return 0.1 * omegaC}}
    var dOmega:Double {get {return 0.1 * omegaSignal}}
    var omegaArray:[Double] {get {return Array(stride(from: 0.0, through:    omegaMax, by: dOmega))}}

The variable dOmega is expected to hold the value 0.001. The array is expected to have the size of 1001 elements, where the last element should have the value 10.0

However, these assumptions are not true, as you can see in the following code section:

let demo = Demo()
let nbrOfElements = demo.omegaArray.count
let lastElement = demo.omegaArray[nbrOfElements-1]

print("\(nbrOfElements)")  // -> 1000
print("\(lastElement)")    // -> 9.990000000000002

What is going on there? Inspecting dOmega gives the answer.

print("\(demo.dOmega)")   // -> 0.010000000000000002

The increment value dOmega is not exactly 0.01 as expected, but has a very small approximation error, which is OK for a Double. However this leads to the situation, that the expected Array element 1001 would have the value 10.000000000000002, which is bigger than the given maximum value of 10.0 and so this element 1001 is not generated. Depending whether the variables in the stride function have rounding errors or not, the result is the expected one or not.

My question is: What is the right way to use stride with Doubles to get in any case the expected result?

Heinz M.
  • 668
  • 3
  • 8
  • https://stackoverflow.com/questions/588004/is-floating-point-math-broken – Martin R Feb 16 '19 at 10:23
  • Heinz, having worked with similar issues on various occasions, my advice is to construct an integer sequence which has exactly the right number of elements, assign the first and last elements directly (just copy them, no arithmetic), and then calculate something like (first value) + (k/length) times step in between, for k = 1, ..., length - 1. This avoids questions about whether stuff adds up exactly. – Robert Dodier Mar 18 '23 at 00:00

1 Answers1

1

You are using the wrong overload of stride!

There are two overloads:

You should have used the second one, because it will include the parameter through, subject to floating point imprecisions, whereas the first one will never include the parameter to.

print(Array(stride(from: 0, through: 10.0, by: 0.001)).last!) // 10.0

The little difference you see is just because of the imprecise nature of Double.

Edit:

Since it turns out that your actual problem is also due to floating point maths being broken, I'd suggest using Decimals instead, which represents a base-10 number, and doesn't have the precision issues that Double has.

let omegaMax: Decimal = 10.0
let omegaC: Decimal = 1.0
var omegaSignal: Decimal {
    0.1 * omegaC
}
var dOmega: Decimal {
    0.1 * omegaSignal
}
var omegaArray:[Decimal] {
    Array(stride(from: 0, through: omegaMax, by: dOmega))
}

You can easily convert this to [Double]:

let array = demo.omegaArray.map { ($0 as NSNumber).doubleValue }

Again, the results are subject to floating point imprecisions, but for the numbers you are using, this shouldn't be a problem.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Sorry, there was a typo. If I use through this misbehaviour occurs. I corrected the code example. – Heinz M. Feb 16 '19 at 12:06
  • I don’t quite understand. What exactly do you want the resulting array to contain? If `Double` doesn’t work, you could just do this with `Int` and then divide each element by 1000. @HeinzM. – Sweeper Feb 16 '19 at 12:11
  • Testing shows that the final value from stride( from: 0, through: 0.9, by: 0.9 / 3 ) is .8999999999, not 9.0, so there is no guarantee that the final value is the "through" value. And that's using a "by" value that you would expect to produce the given "through" value. Using a "by" value that is unrelated to the "through" value could result in the last stride value being far from the "through" value. In other words, stride() does not include the parameter "through", it only guarantees to not stop prior to the "through" value is "through" is the final value calculated. – David Rector Mar 17 '23 at 23:47
  • 1
    @DavidRector Yeah, this was back when my wording was still not very precise. `0.9 / 3` probably evaluates to a value that is a little less than 0.3, making it not reach 0.9 exactly. – Sweeper Mar 17 '23 at 23:52