1. 程式人生 > >震驚!C++/C中輸出浮點數時的四舍五入竟可以被hack!

震驚!C++/C中輸出浮點數時的四舍五入竟可以被hack!

bsp cst www. family st2 print 控制 double 一份

假如我們遇到了這樣一道題:


題目描述】

給你一個浮點數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;
}

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


同步發表於:洛谷

震驚!C++/C中輸出浮點數時的四舍五入竟可以被hack!