引言
前一陣做了一個有理數四則混合運算的程序(詳見:用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