報表數字與應用計算不一致、匯入匯出金額對不上、稅務計算差異……許多「離奇」Bug 根源於 **資料類型 (int, decimal, float) × 四捨五入函數 (ROUND, CEILING, FLOOR) × 精度參數** 的組合炸彈。本文透過系統性實測案例,建立精確的心智模型。
| 函數 | 語意 | 關鍵字 |
|---|---|---|
ROUND(value, length [,function]) | 四捨五入至指定精度 (length>0 為小數位,<0 為整數位)。第三參數 function=1 為截斷。 | 銀行家算法 (Banker's Rounding) 視實作而定 |
CEILING(value) | 返回 ≥ value 的最小整數 (向上取整,負數向零方向)。 | 天花板 |
FLOOR(value) | 返回 ≤ value 的最大整數 (向下取整,負數遠離零方向)。 | 地板 |
DECLARE @v INT = 6; SELECT ROUND(@v,1), CEILING(@v), FLOOR(@v); -- 6, 6, 6
DECLARE @v INT = -11; SELECT ROUND(@v,1), CEILING(@v), FLOOR(@v); -- -11, -11, -11
整數無小數位,三函數皆返回原值。
DECLARE @v INT = -11.5; -- 隱含轉換為 -11 (截斷小數)!
SELECT ROUND(@v,2), CEILING(@v), FLOOR(@v); -- -11, -11, -11
**關鍵教訓:** 變數宣告類型決定儲存值,**小數常值指派給 INT 會靜默截斷**,非四捨五入!
DECLARE @v DECIMAL(10,2) = 11.05;
SELECT ROUND(@v,1), ROUND(@v,2), ROUND(@v,3), CEILING(@v), FLOOR(@v);
-- 11.10 | 11.05 | 11.05 | 12 | 11
DECLARE @v DECIMAL(10,2) = -14.46;
SELECT ROUND(@v,1), ROUND(@v,2), ROUND(@v,3), CEILING(@v), FLOOR(@v);
-- -14.50 | -14.46 | -14.46 | -14 | -15
ROUND(@v,1) 看第 2 位小數 (5/6) 決定進位。CEILING/FLOOR 作用於**完整數值**,忽略精度參數,負數方向相反。
DECLARE @v DECIMAL(10,10) = 0.5432167890;
SELECT ROUND(@v,1), ROUND(@v,2), ROUND(@v,3), ROUND(@v,4), ROUND(@v,5),
ROUND(@v,6), ROUND(@v,7), ROUND(@v,8), ROUND(@v,9), ROUND(@v,10),
CEILING(@v), FLOOR(@v);
-- 0.5 | 0.54 | 0.543 | 0.5432 | 0.54322 | 0.543217 | 0.5432168 | 0.54321679 | 0.543216789 | 0.543216789 | 1 | 0
精度參數超過實際小數位時,結果穩定不再變化。
DECLARE @v FLOAT(10) = 0.1234567890;
SELECT ROUND(@v,1), ROUND(@v,2), ROUND(@v,3), ROUND(@v,4), ROUND(@v,5),
ROUND(@v,6), ROUND(@v,7), ROUND(@v,8), ROUND(@v,9), ROUND(@v,10),
CEILING(@v), FLOOR(@v);
-- 0.1 | 0.12 | 0.123 | 0.1235 | 0.12346 | 0.123457 | 0.1234568 | 0.12345679 | 0.123456791 | 0.123456791 | 1 | 0
警告: `FLOAT`/`REAL` 為 IEEE 754 二進制浮點,**0.1 無法精確表示**,累加運算會產生誤差,**嚴禁用於財務/精確計算**。
SELECT ROUND(123.456, 2, 1); -- 123.45 (截斷)
SELECT ROUND(123.456, 2, 0); -- 123.46 (四捨五入,預設)
CREATE TABLE #RoundingTest (TypeName VARCHAR(20), Val SQL_VARIANT);
INSERT #RoundingTest VALUES
('INT', 6), ('INT', -11),
('DECIMAL(10,2)', 11.05), ('DECIMAL(10,2)', -14.46),
('DECIMAL(10,10)', 0.5432167890),
('FLOAT', 0.1234567890);
SELECT TypeName, Val,
ROUND(CAST(Val AS DECIMAL(38,10)), 2) AS Round2,
CEILING(CAST(Val AS DECIMAL(38,10))) AS Ceil,
FLOOR(CAST(Val AS DECIMAL(38,10))) AS Floor
FROM #RoundingTest;
四捨五入不是數學課本上的單一真理,而是 **資料類型 × 函數語意 × 參數設定** 的工程決策。**明確宣告 DECIMAL、統一使用 ROUND(length, 0)、禁用 FLOAT 處理金額、測試邊界值**,是避免「分不清」的唯一鋼鐵律。