摘自編程之美
在計算機中,使用float或者double來存儲小數是不能得到精確值的。如果你希望得到精確計算結果,最好是用分數形式來表示小數。有限小數或者無限循環小數都可以轉化爲分數。比如:
0.9=9/10
0.333(3)=1/3(括號中的數字表示是循環節)
當然一個小數可以用好幾種分數形式來表示。如:0.333(3)=1/3=3/9
給定一個有限小數或者無限循環小數,你能否以分母最小的分數形式來返回這個小數呢?如果輸入爲循環小數,循環節用括號標記出來。下面是一些可能的輸入數據,如0.3、0.30、0.3(000)、0.3333(3333)、……
分析與解法
拿到這樣一個問題,我們往往會從最簡單的情況入手,因爲所有的小數都可以分解成一個整數和一個純小數之和,不妨只考慮大於0,小於1的純小數,而且暫時不考慮分子和分母的約分,先設法將其表示爲分數形式,然後再進行約分。題目中輸入的小數,要麼爲有限小數X=0.a1a2…an,要麼爲無限循環小數X=0.a1a2…an(b1b2…bm),X的表示式中的字母a1a2…an,b1b2…bm都是0~9的數字,括號部分(b1b2…bm)表示循環節,我們需要處理的就是以上兩種情況。
對於有限小數X=0.a1a2…an來說,這個問題比較簡單,X就等於(a1a2…an)/10^n。
對於無限循環小數X=0.a1a2…an(b1b2…bm)來說,其複雜部分在於小數點後同時有非循環部分和循環部分,我們可以做如下的轉換:
X=0.a1a2…an(b1b2…bm)
=>10^n*X=a1a2…an.(b1b2…bm)
=>10^n*X=a1a2…an+0.(b1b2…bm)
=>X=(a1a2…an+0.(b1b2…bm))/10^n
對於整數部分a1a2…an,不需要做額外處理,只需要把小數部分轉化爲分數形式再加上這個整數即可。對於後面的無限循環部分,可以採用如下方式進行處理:
令Y=0.b1b2…bm,那麼
10^m*Y=b1b2…bm.(b1b2…bm)
=>10^m*Y=b1b2…bm+0.(b1b2…bm)
=>10^m*Y-Y=b1b2…bm
=>Y=b1b2…bm/(10^m-1)
將Y代入前面的X的等式可得:
X=(a1a2…an+Y)/10^n
=(a1a2…an+b1b2…bm/(10^m-1))/10^n
=((a1a2…an)*(10^m-1)+(b1b2…bm))/((10^m-1)*10^n)
至此,便可以得到任意一個有限小數或無限循環小數的分數表示,但是此時分母未必是最簡的,接下來的任務就是讓分母最小,即對分子和分母進行約分,這個相對比較簡單。對於任意一個分數A/B,可以簡化成(A/Gcd(A,B))/(B/Gcd(A,B)),其中Gcd函數爲求A和B的最大公約數,這就涉及本書中的算法(2.7節“最大公約數問題”),其中有很巧妙的解法,請讀者閱讀具體的章節,這裏就不再贅述。
綜上所述,先求得小數的分數表示方式,再對其分子分母進行約分,便能夠得到分母最小的分數表現形式。
例如,對於小數0.3(33),根據上述方法,可以轉化爲分數:
0.3(33)
=(3*(10^2-1)+33)/((10^2-1)*10)
=(3*99+33)/990
=1/3
對於小數0.285714(285714),我們也可以算出:
0.285714(285714)
=(285714*(10^6-1)+285714)/((10^6-1)*10^6)
=(285714*999999+285714)/999999000000
=285714/999999
=2/7