Search code examples
iosswiftfoundationinfinite

Rounding an Infinite Number?


This question is related to an earlier question however I am receiving an infinite number not related to a divided by 0 problem. For example, the code below prints 4.5300000000000002 in the console but is flagged as .isInfinate and therefore I cannot store using Codable. How can I derive 4.53 (as a double) from this example?

  //Calculation  
     func calculateMaximumAndAverageSkatingEfficiency() {

            let heartRateUnit:HKUnit = HKUnit(from: "count/min")
            let heartRatesAsDouble = heartRateValues.map { $0.quantity.doubleValue(for: heartRateUnit)}
            let maxHeartRate = heartRatesAsDouble.max()
            guard let maxHeartRateUnwrapped = maxHeartRate else { return }

            maximumEfficiencyFactor = ((1760.0 * (maxSpeed / 60)) / maxHeartRateUnwrapped).round(to: 2)

            guard let averageIceTimeHeartRateUnwrapped = averageIceTimeHeartRate else { return }

            averageEfficiencyFactor = ((1760.0 * (averageSpeed / 60)) / averageIceTimeHeartRateUnwrapped).round(to: 2)

        }

    //Round extension I am using
    extension Double {

        func round(to places: Int) -> Double {
            let divisor = pow(10.0, Double(places))
            return Darwin.round(self * divisor) / divisor
        }

    }

//Usage 
  if let averageEfficiencyFactorUnwrapped = averageEfficiencyFactor {
        if averageEfficiencyFactorUnwrapped.isFinite {
            hockeyTrackerMetadata.averageEfficiencyFactor = averageEfficiencyFactorUnwrapped.round(to: 2)
        } else {
            print("AEF is infinite")
        }

    }

Solution

  • Double cannot precisely store 4.53 for the same reason that you cannot precisely write down the value 1/3 in decimal. (See What Every Programming Should Know About Floating-Point Arithmetic if this is unclear to you.)

    If you want your rounding to be in decimal rather than binary, then you need to use a decimal type. See Decimal.

    Your round(to:) method is incorrect because it assumes "places" are decimal digits. But Double works in binary digits. I believe what you want is this:

    extension Double {
        func round(toDecimalPlaces places: Int) -> Decimal {
            var decimalValue = Decimal(self)
            var result = decimalValue
            NSDecimalRound(&result, &decimalValue, places, .plain)
            return result
        }
    }
    

    Note that 4.53 is in no way "infinite." It is somewhat less than 5. I don't see anywhere in your code that it should generate an infinite value. I would double-check how you're determining that.