1. 程式人生 > 實用技巧 >分數化迴圈小數C++實現

分數化迴圈小數C++實現

引言

前一陣做了一個有理數四則混合運算的程式(詳見:用C++實現的有理數(分數)四則混合運算計算器),以分數形式呈現運算結果。這次新增以迴圈小數形式呈現運算結果。例如:

Please input a rational expression to calculate its value or input q to quit:

67+34*(23-78/(54*5))-2*(5/37-6.5789)
= 67 + 34 * ( 23 - 78 / ( 270 ))-2*(5/37-6.5789)
= 67 + 34 * ( 23 - 13/45 )-2*(5/37-6.5789)
= 67 + 34 * ( 1022/45 )-2*(5/37-6.5789)

= 67 + 34748/45 -2*(5/37-6.5789)
= 37763/45 -2*(5/37-6.5789)
= 37763/45 - 2 * ( 5/37 -6.5789)
= 37763/45 - 2 * ( 5/37 - 65789/10000 )
= 37763/45 - 2 * ( -2384193/370000 )
= 37763/45 - -2384193/185000
= 852[108737/1665000] {852.0653`075}

演算法分析

由分數化迴圈小數不像由迴圈小數化分數那麼簡單明瞭。比如 0.2`142857 這個小數,它等於 0.2 加上 0.0`142857,用分數表示分別是 2/10 和 142857/9999990,化簡即是 1/5 和 1/70,相加並化簡得到分數運算結果:3/14。

現在考察由 3/14 如何反推出0.2`142857。

1、3 / 14 = 0...3 商為0,得到小數形式結果的整數部分,餘數不為0,繼續

2、30 / 14 = 2...2 商為2,得到小數點後第1位小數,餘數不為0,繼續

3、20 / 14 = 1...6 商為1,得到小數點後第2位小數,餘數不為0,繼續

4、60 / 14 = 4...4 商為4,得到小數點後第3位小數,餘數不為0,繼續

5、40 / 14 = 2...12商為2,得到小數點後第4位小數,餘數不為0,繼續

6、120 / 14 = 8...8 商為8,得到小數點後第5位小數,餘數不為0,繼續

7、80 / 14 = 5...10 商為5,得到小數點後第6位小數,餘數不為0,繼續

8、100 / 14 = 7...2 商為7,得到小數點後第7位小數,餘數不為0,繼續

9、20 / 14 = 1...6 商為1,得到小數點後第8位小數,餘數不為0,繼續

......

上述試商過程,當進行到第8步時,得到的餘數是2,和第2步得到的餘數相同,於是從第9步開始就會周而復始重複第3步到第8步的過程。說明已經找到了迴圈小數的迴圈體。

演算法實現

在 SFraction 裡增加 toDecimal 介面:

 1 struct SFraction
 2 {
 3     u64 numerator;
 4     u64 denominator;
 5     bool bNegative;
 6 
 7     SFraction() {
 8         numerator = 0;
 9         denominator = 1;
10         bNegative = false;
11     }
12 
13     std::string toStr(bool bFinal = false) const;
14     std::string toDecimalStr() const;
15 };

SFraction 的 toDecimal 介面實現如下:

 1 std::string SFraction::toDecimalStr() const
 2 {
 3     std::ostringstream oStream;
 4     if (bNegative)
 5     {
 6         oStream << "-";
 7     }
 8     u64 quotient = numerator / denominator;
 9     oStream << quotient;
10     u64 remainder = numerator % denominator;
11     if (remainder == 0)
12     {
13         return oStream.str();
14     }
15     oStream << ".";
16     u64 pos = 0;
17     u64 posMatched = 0;
18     std::map<u64, u64> mapRemainderPos;
19     std::vector<u64> vecQuotient;
20     while (true)
21     {
22         mapRemainderPos[remainder] = pos++;
23         remainder *= 10;
24         vecQuotient.push_back(remainder / denominator);
25         remainder = remainder % denominator;
26         if (remainder == 0)
27             break;
28         std::map<u64, u64>::iterator it = mapRemainderPos.find(remainder);
29         if (it != mapRemainderPos.end())
30         {
31             posMatched = it->second;
32             break;
33         }
34     }
35     if (remainder == 0)
36     {
37         for (size_t idx = 0; idx < vecQuotient.size(); ++idx)
38             oStream << vecQuotient[idx];
39         return oStream.str();
40     }
41     size_t idx = 0;
42     for (; idx < posMatched; ++idx)
43         oStream << vecQuotient[idx];
44     oStream << "`";
45     for (; idx < vecQuotient.size(); ++idx)
46         oStream << vecQuotient[idx];
47     return oStream.str();
48 }

然後對 SFraction 的 toStr 介面稍作調整,即可達到增加迴圈小數形式的呈現效果:

 1 std::string SFraction::toStr(bool bFinal) const
 2 {
 3     std::ostringstream oStream;
 4     if (bNegative)
 5     {
 6         oStream << "-";
 7     }
 8     if (denominator == 1)
 9     {
10         oStream << numerator;
11         return oStream.str();
12     }
13     if (!bFinal)
14     {
15         oStream << numerator << "/" << denominator;
16         return oStream.str();
17     }
18     if (numerator < denominator)
19     {
20         oStream << numerator << "/" << denominator << " {" << toDecimalStr() << "}";
21         return oStream.str();
22     }
23     u64 quotient = numerator / denominator;
24     u64 remainder = numerator % denominator;
25     oStream << quotient << "[" << remainder << "/" << denominator << "] {" << toDecimalStr() << "}";
26     return oStream.str();
27 }

完整程式碼檔案提取位置

https://github.com/readalps/RationalCalculator