在 VC++(MSVC)中,float/double 轉 int 時,常出現結果比預期少 1的現象(如 11.40f * 100 → 1139)。這並非編譯器 Bug,而是IEEE 754 浮點數表示誤差 + 截斷語意的必然結果。
float f = 11.40f;
int i = (int)(f * 100); // 預期 1140,實際 1139
float f2 = 11.41f;
int i2 = (int)(f2 * 100); // 1140 ✓
// 負數對稱:-11.40f * 100 → -1139(絕對值變大 1)
11.40 在二進位浮點數為 11.3999996185302734375(單精度)。f * 100 產生 1139.99993896484375。1139。「差 1」發生條件:真實值極接近整數但略小於該整數。大多數數值不會剛好落在這種邊界,故看似隨機。
原文觀察到將中間結果存入類別成員(而非區域變數)可避免誤差。實際上這是因為:
float 時發生雙重捨入(80-bit → 32-bit → 截斷)。/fp:precise(預設)允許保留中間精度;/fp:strict 或 /arch:SSE2 強制使用 SSE 32-bit 運算,行為一致。| 方法 | 程式碼 | 優點 | 缺點 |
|---|---|---|---|
| 四捨五入 | int i = (int)(f * 100 + 0.5f); | 簡單、通用 | 負數需改 -0.5f;極大值溢位 |
| std::round | int i = (int)std::round(f * 100); | 標準庫、正確處理正負 | 需 C++11、函數呼叫開銷 |
| std::lround | long l = std::lround(f * 100); | 直接得長整數 | 同上 |
| 原文「誤差檢測法」 | 見下方 | 針對「差 1」專用 | 邏輯繁瑣、僅修正邊界、掩蓋根本問題 |
| 定點數/整數運算 | int i = (int)(f * 100.0 + 1e-6); | 避免浮點 | 需領域知識調整 epsilon |
int float_to_int(float f) {
int i = (int)f;
float ferror = f - (float)i;
if (std::fabs(ferror) > 0.99f) { // 檢測「差 1」邊界
if (f > 0) ++i;
else --i;
}
return i;
}
缺點:0.99 門檻為經驗值,無法涵蓋所有邊界;對大數值失效;掩蓋了「應該用四捨五入」的語意。
int64_t 存分/厘)或 boost::multiprecision::cpp_dec_float。int i = (int)std::round(value);(C++11)或 (int)(value + 0.5f)(正數)。/arch:SSE2(x86 預設)或 /fp:strict,統一浮點語意,減少平台差異。x.00, x.01, x.99)建立回歸測試。「差 1」本質是 浮點表示誤差 + 截斷語意 的交互結果,非編譯器缺陷。
正確語意請用四捨五入(std::round),關鍵領域請用定點數/十進位浮點。