2

Swift 4.2 Xcode 10β2

Background

I'm in the process of putting together an astronomy related API to handle the maths in an app I'm working one. The intention was to use the Measurement class to handle the input and output of angles and distances. In this way, the user could enter data using their preferred units (e.g degrees or radians, miles or kilometers or any combination) and get output in the same units.

This appeared to work really well until I tested the results. On a relatively simple calculation like the distance from the centre of the Earth at a given latitude I was getting errors in the order of two kilometers.

It took a couple of hours (because I assumed it was rounding errors in the calculations) but I eventually traced the problem back to the Measurment class itself. The conversion factor between degrees and radians is inaccurate. It should be 180.0 / Double.pi (57.29577951308232...) but is 57.9528.

Code example

print(UnitAngle.radians.converter.baseUnitValue(fromValue: 1.0))  // Inaccurate.
print(180.0 / Double.pi)                                          // Accurate.

Questions

So, two (ok three) questions:

  1. Is this a bug? It must be a bug. It's far too inaccurate.
  2. Is it possible to set the conversion factor to a different value (or subclass Measurement to achieve the same).
  3. Should I roll my own Measurement class or just abandon the idea of unit conversion and stick to SI units internally? (Would would be sad because, otherwise, it does exactly (sic) what I want.)
Vince O'Sullivan
  • 2,611
  • 32
  • 45

1 Answers1

2
import Foundation

print(UnitAngle.radians.converter.baseUnitValue(fromValue: 1.0)) // 57.2958
print(180.0 / .pi) // 57.2957795130823
print(UnitAngle.radians.converter.baseUnitValue(fromValue: .pi)) // 180.00006436155007

Is this a bug? It must be a bug. It's far too inaccurate.

I would say yes. All Measurement APIs take Double arguments and return Double values. This large deviation cannot simply be explained with floating point inaccuracies, and makes the result useless for serious work with trigonometric functions.

Interestingly, the result is correct on Ubuntu Linux (where the open source implementation from Units.swift is used):

Welcome to Swift version 4.2-dev (LLVM ec4d8ae334, Clang 5a454fa3d6, Swift 0ca361f487). Type :help for assistance.
  1> import Foundation

  2> print(UnitAngle.radians.converter.baseUnitValue(fromValue: 1.0))
57.29577951308232

  3> print(180.0 / .pi)
57.29577951308232

  4> print(UnitAngle.radians.converter.baseUnitValue(fromValue: .pi))
180.0

Is it possible to set the conversion factor to a different value (or subclass Measurement to achieve the same).

Similarly as in Swift - Measurement convert(to:) miles to feet gives wrong result, you can define your own unit:

extension UnitAngle {
    static let preciseRadians = UnitAngle(symbol: "rad",
                        converter: UnitConverterLinear(coefficient: 180.0 / .pi))
}

print(UnitAngle.preciseRadians.converter.baseUnitValue(fromValue: 1.0)) // 57.2957795130823
print(UnitAngle.preciseRadians.converter.baseUnitValue(fromValue: .pi)) // 180.0

Should I roll my own Measurement class or just abandon the idea of unit conversion and stick to SI units internally?

That is for you to decide :)

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Errata: "preciseRadians" above should be a UnitAngle not a UnitLength. Once that correction is made then accuracy increases by at least 8 magnitudes. Thx – Vince O'Sullivan Jul 04 '18 at 10:23