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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With