震驚!C++/C中輸出浮點數時的四舍五入竟可以被hack!
假如我們遇到了這樣一道題:
【題目描述】
給你一個浮點數f,輸出它保留n位小數(四舍五入)後的結果。
【輸入格式】
輸入兩個數,分別為f和n。
【輸出格式】
一個數,即最終結果。
【輸入樣例】
3.15 1
【輸出樣例】
3.2
【說明】
f可以用double儲存,1<=n<=5。
看過題後,LYF dalao吐槽到:
fAKe?這麽簡單?信不信老子用C++30s過。。。
(然而事情並沒有這麽簡單。。。)
LYF dalao似乎在20後就寫出來了:
#include <iostream> #include <iomanip> using namespace std; int n; double f; int main() { cin >> f >> n; cout << fixed << setprecision(n) << f << endl; return 0; }
LYF他測試了樣例,結果是:
(以下是控制臺內容)
3.15 1
3.1--------------------------------
Process exited after -INF seconds with return value 0
請按任意鍵繼續. . .
(不信的同學可以到這裏試一試)
LYF又是一頓吐槽:
蛤?這是啥玩意啊?用printf試試。。。
他又寫了一份代碼:
#include <iostream> #include <cstdio> using namespace std; int n; double f; char com[7]="%. lf\n"; int main() { cin >> f >> n; com[2] = n + ‘0‘; printf(com, f); return 0; }
結果。。。毫無變化
(以下是控制臺內容)
3.15 1
3.1--------------------------------
Process exited after -INF seconds with return value 0
請按任意鍵繼續. . .
(同樣,不信的話可以到這裏試試)
LYF dalao陷入了崩潰狀態。。。
那麽,我們回首這場悲劇,來探究問題的根源。
蒟蒻我寫了一份代碼,來研究C++/C(cout/printf)在對浮點數進行四舍五入Output時情況:
#include <iostream> #include <iomanip> #include <cstdio> using namespace std; char com[7]="%. lf\n"; int main() { cout << "It‘s cout:" << endl; out << fixed << setprecision(1) << "base: " << 3.15 << endl; cout << fixed << setprecision(1) << "test1:" << 3.150 << endl; cout << fixed << setprecision(1) << "test2:" << 3.153 << endl; cout << fixed << setprecision(1) << "test3:" << 3.155 << endl; cout << fixed << setprecision(1) << "test4:" << 3.159 << endl; cout << fixed << setprecision(2) << "test5:" << 3.155 << endl; cout << fixed << setprecision(2) << "test6:" << 3.15533 << endl; cout<<endl; printf("It‘s printf\n"); printf("base: "); com[2] = 1 + ‘0‘; printf(com, 3.15); printf("test1:"); com[2] = 1 + ‘0‘; printf(com, 3.150); printf("test1:"); com[2] = 1 + ‘0‘; printf(com, 3.153); printf("test1:"); com[2] = 1 + ‘0‘; printf(com, 3.155); printf("test1:"); com[2] = 1 + ‘0‘; printf(com, 3.159); printf("test1:"); com[2] = 2 + ‘0‘; printf(com, 3.155); printf("test1:"); com[2] = 2 + ‘0‘; printf(com, 3.15533); return 0; }
(老規矩,發放測試入口)
運行結果如下:
(以下是控制臺內容)
It‘s cout:
base: 3.1
test1:3.1
test2:3.2
test3:3.2
test4:3.2
test5:3.15
test6:3.16It‘s printf
base: 3.1
test1:3.1
test1:3.2
test1:3.2
test1:3.2
test1:3.15
test1:3.16--------------------------------
Process exited after 0.INF seconds with return value 0
請按任意鍵繼續. . .
當我們分析上面的運行結果後,可總結如下規律:
- C++/C(cout/printf)在對浮點數進行四舍五入Output時所發生的現象一樣,即進行的操作相同。
- 若要四舍五入保留n位小數,當小數後第n+1位數x滿足0<=x<=4或6<=x<=9時,C++/C可正確四舍五入,輸出正確答案。
- 當x=5時,若第n+1位後還有數字(如測試中的3.153),可正確四舍五入;但是如果沒有(如測試中的3.15),可能會輸出錯誤答案(至於為什麽是“可能”,因為我後來發現這種情況下不一定會錯)。
由此,我們可以發現,cout/printf在保留小數的時候,有非常大的問題(後來我了解到C++/C的機制是四舍六入五成雙,不是四舍五入),所以我們最好不要直接用它們四舍五入了。
蛤?你問我該用啥?
當然是這個啦:
inline double FourCutFiveKeep(double num,int t)
{
int pow10t=1;
while(t!=0)
{
pow10t *= 10;
t--;
}
return (int)(num * (double)pow10t + 0.5) / (double)pow10t;
}
(於是,我們有了一開始那道題的正解)
異 同步發表於:洛谷
震驚!C++/C中輸出浮點數時的四舍五入竟可以被hack!