[Happy DSA] 2個浮點數的最大公約數的算法小結

我們知道2個整數有最大公約數(GCD)和最小公倍數(LCM)的說法。而且最大公約數GCD有一個公認的歐拉算法GCD。它的算法如下:

GCD(a, b) 
{
    return b ? GCD(b, a % b) : a;
}
有了GCD,當然也很好求解LCM。

現在,問題來了,給定2個浮點數,怎麼求解它們的GCD?


1. 數學上的方法

一個比較的想法就是,既然我們已有的工具是整數的GCD求法,那麼我們就想辦法將浮點數轉化爲整數。那如何將一個浮點數轉化爲一個整數來表示呢,甚者說用幾個整數來表示呢?我們知道,浮點數m.n是由整數部分m和小數部分n組成的,這個是我們小學學習的東西(還好沒還給老師)。一種轉化方法就是將小數部分n乘以一個10冪次方數,冪爲小數點後即n的數字個數,就是m + n * 10^(n.length)。所以m.n就可以表示爲(m.n*10^(n.length)) / (10^(n.length))。

這種方法也就是將浮點數轉化爲它的另一種形式:分數(fraction)。分數由分子(numerator)和分母(denomitor)組成(又是小學的知識)。


有了一個浮點數可以轉化爲分數的方法,那麼我們可以將2個浮點數都轉化爲它們的最簡的分數形式。回到最初的問題(2個浮點數的GCD):我們可以知道2個浮點數中小數的最長位數,然後都乘以10次冪的這個數,讓2個浮點數轉化爲2個整數,然後在整數下求解GCD,之後再除以之前那個10次冪數。

更一般思路是:將2個浮點數轉化爲2個最簡分數(這裏是最簡分數),然後將2個分數進行通分(就是求解2個分數的分母的LCM),之後再求解2個分子的GCD。最後得到的分子的GCD與分母的LCM表示的分數即爲最終2個浮點數的GCD的分數表示形式。


2. 程序實現

有了上面數學上的方法,程序上實現應該來說不是難事。然而還是有些地方需要注意的。

上述方法中,最重要的就是浮點數轉化爲分數的那一步。如果採用將2個浮點數同乘以一個10次冪數來轉化整數,程序上可能就會有點麻煩。

a) 我們得知道那2個浮點數小數部分的有多少位。

b) 另外知道位數後,有一種懷疑就是這個位數是否準確。因爲計算機表示浮點數總是不精確的,比如1.0,可能會表示爲0.999999999...,也可能表示爲1.000...1。如果貿然將它強行轉化爲整數,得到的可能並不是我們所期望的。

所以說,上面簡單的方法行不通,我們得尋找一個精度更高的方法,來將浮點數轉化爲分數。

數學上有一個方法叫做continued fraction,它說任何一個數,都可以表示成它的整數部分與另一個數的倒數之和,然後那另一個數,又可以表示成它的整數部分和另另一個數的倒數之和,一直這樣下去:http://en.wikipedia.org/wiki/Continued_fraction

In mathematics, a continued fraction is an expression obtained through an iterative process of representing a number as the sum of its integer part and the reciprocal of another number, then writing this other number as the sum of its integer part and another reciprocal, and so on.

這種方法,可以滿足我們程序在精度上的需求。


LUA代碼實現如下:

cntFrac = {}
function cntFrac.caclGCD(a, b)
   local nu, de = cntFrac.float2frac(a, 1e-8, 10)
   local cnu, cde = cntFrac.float2frac(b, 1e-8, 10)
   cnu, cde = cntFrac.getGCD(nu, de, cnu, cde)
   return cnu, cde

function cntFrac.float2frac(x, eps, n)
    local i = 1
    local fx, err = x, 1.
    local nu, de = 0, 1
    local a = {}
    a[1] = math.floor(x)
    fx = x - a[1]
    x, err = fx, fx
    while math.abs(err) > eps and i < n do
        i = i + 1
        fx = 1./ fx
        a[i] = math.floor(fx)
        fx = fx - a[i]
        nu, de = cntFrac.continuedFrac(a, i)
        err = x - nu / de
    end
    nu = nu + a[1] * de
    nu = math.floor(nu + 1e-10)
    de = math.floor(de + 1e-10)
    return nu, de
end

function cntFrac.continuedFrac(a, n)
    if 1 == n then return 0, 1 end
    local nu, de = 1, a[n]
    local c
    for i = n - 1, 2, -1 do
        nu = nu + de * a[i]
        c = nu; nu = de; de = c
        nu = math.floor(nu + 1e-10)
        de = math.floor(de + 1e-10)
    end
    return nu, de
end

function cntFrac.getGCD(a1, b1, a2, b2)
    local db = cntFrac.gcd(b1, b2);
    db = math.floor(b1 * b2 / db + 1e-10) -- LCM for denominator
    a1 = a1 * math.floor(db / b1 + 1e-10)  
    a2 = a2 * math.floor(db / b2 + 1e-10)
    local da = cntFrac.gcd(a1, a2)          -- GCD for numerator
    return da, db
end

function cntFrac.gcd(a, b)
    a = math.floor(a + 1e-10)
    b = math.floor(b + 1e-10)
    if (b < 1e-8) then
        return a
    else
        return cntFrac.gcd(b, math.mod(a, b))
    end
end



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章