震驚!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.16

It'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;
}

(於是,我們有了一開始那道題的正解)


 

 同步發表於:洛谷


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM