(The code has been updated for Swift 4 and later.)
First of all, there is no GCD or LCM for floating point numbers. You
have to convert the input to rational numbers first.
This is not as easy as it sounds, because decimal fractions like 0.7
cannot be represented exactly as a binary floating point number and
would be stored as something like 0.69999999999999996
in a Double
.
So it is not completely obvious how to get from there to 7/10
.
It is therefore necessary to specify a precision. Then you can
use
Continued Fractions
to efficiently create a (finite or infinite) sequence of fractions hn/kn that are arbitrary good approximations to a given real number x.
Here is a translation of this JavaScript implementation to Swift:
typealias Rational = (num : Int, den : Int)
func rationalApproximationOf(_ x0 : Double, withPrecision eps : Double = 1.0E-6) -> Rational {
var x = x0
var a = floor(x)
var (h1, k1, h, k) = (1, 0, Int(a), 1)
while x - a > eps * Double(k) * Double(k) {
x = 1.0/(x - a)
a = floor(x)
(h1, k1, h, k) = (h, k, h1 + Int(a) * h, k1 + Int(a) * k)
}
return (h, k)
}
Examples:
rationalApproximationOf(0.7) // (7, 10) i.e. 7/10
rationalApproximationOf(0.142857) // (1, 7) i.e. 1/7
I have set the default precision to 1.0E-6, but you can adjust that
to your needs.
Then you need functions for the GCD (greatest common divisor)
and LCM (lowest common multiple). Here are simple implementation:
// GCD of two numbers:
func gcd(_ a: Int, _ b: Int) -> Int {
var (a, b) = (a, b)
while b != 0 {
(a, b) = (b, a % b)
}
return abs(a)
}
// GCD of a vector of numbers:
func gcd(_ vector: [Int]) -> Int {
return vector.reduce(0, gcd)
}
// LCM of two numbers:
func lcm(a: Int, b: Int) -> Int {
return (a / gcd(a, b)) * b
}
// LCM of a vector of numbers:
func lcm(_ vector : [Int]) -> Int {
return vector.reduce(1, lcm)
}
With all these utilities, your function can now be implemented as
func simplifyRatios(_ numbers : [Double]) -> [Int] {
// Normalize the input vector to that the maximum is 1.0,
// and compute rational approximations of all components:
let maximum = numbers.map(abs).max()!
let rats = numbers.map { rationalApproximationOf($0/maximum) }
// Multiply all rational numbers by the LCM of the denominators:
let commonDenominator = lcm(rats.map { $0.den })
let numerators = rats.map { $0.num * commonDenominator / $0.den }
// Divide the numerators by the GCD of all numerators:
let commonNumerator = gcd(numerators)
return numerators.map { $0 / commonNumerator }
}
Examples:
simplifyRatios([0.7, 0, -0.7]) // [1, 0, -1]
simplifyRatios([24, 12, 0]) // [2, 1, 0]
simplifyRatios([1.3, 0.26, 0.9]) // [65, 13, 45]