編程之美2:精確表達浮點數

摘自編程之美

 

在計算機中,使用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

 

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