Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - Measurement convert(to:) miles to feet gives wrong result

I've been using the Measurement object to convert from mostly lengths. But I have a strange issue. If I convert from miles to feet I get almost the right answer.

import Foundation

let heightFeet = Measurement(value: 6, unit: UnitLength.feet) // 6.0ft
let heightInches = heightFeet.converted(to: UnitLength.inches) // 72.0 in
let heightMeters = heightFeet.converted(to: UnitLength.meters) // 1.8288 m

let lengthMiles = Measurement(value: 1, unit: UnitLength.miles) // 1.0 mi

let lengthFeet = lengthMiles.converted(to: UnitLength.feet) // 5279.98687664042 ft

// Should be 5280.0

They all work except the last one lengthFeet. In my playground (Xcode Version 9.2 (9C40b)) it returns 5279.98687664042 ft. I also tested in a regular app build and same results.

Any ideas what is going on?

like image 720
Jeff Kempster Avatar asked Mar 19 '18 22:03

Jeff Kempster


2 Answers

The “miles” unit is defined incorrectly in the Foundation library, as can be seen with

print(UnitLength.miles.converter.baseUnitValue(fromValue: 1.0))
// 1609.34

where as the correct value is 1.609344. As a workaround for that flaw in the Foundation library you can define your “better” mile unit:

extension UnitLength {
    static var preciseMiles: UnitLength {
        return UnitLength(symbol: "mile",
                          converter: UnitConverterLinear(coefficient: 1609.344))
    }
}

and using that gives the intended result:

let lengthMiles = Measurement(value: 1, unit: UnitLength.preciseMiles)
let lengthFeet = lengthMiles.converted(to: UnitLength.feet)
print(lengthFeet) // 5280.0 ft

Of course, as Alexander said, rounding errors can occur when doing calculations with the units, because the measurements use binary floating point values as underlying storage. But the reason for that “blatantly off” result is the wrong definition of the miles unit.

like image 157
Martin R Avatar answered Sep 28 '22 14:09

Martin R


You can see the definition of UnitLength here. Every unit of length has a name and a coefficient.

The mile unit has a coefficient of 1609.34, and the foot unit has a coefficient of 0.3048. When represented as a Double (IEEE 754 Double precision floating point number), the closest representations are 1609.3399999999999 and 0.30480000000000002, respectively.

When you do the conversion 1 * 1609.34 / 0.3048, you get 5279.9868766404197 rather than the expected 5280. That's just a consequence of the imprecision of fixed-precision floating point math.

This could be mitigated, if the base unit of length was a mile. This would be incredibly undesirable of course, because most of the world doesn't use this crazy system, but it could be done. Foot could be defined with a coefficient of 5280, which can be represented precisely by Double. But now, instead of mile->foot being imprecise, meter->kilometer will be imprecise. You can't win, I'm afraid.

like image 37
Alexander Avatar answered Sep 28 '22 14:09

Alexander