Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

xcode - Algorithm for LCM of doubles in Swift

I need to turn an array of doubles to ints while keeping their ratios the same and being as simple as possible. For example [0.7, 0, -0.7] should become [1, 0, -1] and [24, 12, 0] should become [2, 1, 0]. I'm not certain if this would involve getting the least common multiple of the doubles or not, and how would this be done if so?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

(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]

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...