T-SQL ROUND Function Bug?

來源:互聯網
上載者:User

先看一個例子:

   1: DECLARE @Value float
   2: SET @Value = 12.1785
   3: SELECT '12.1785' as ValueToRound, ROUND(@Value,3) as RoundedValue
   4: SET @Value = 12.1745
   5: SELECT '12.1745' as ValueToRound,ROUND(@Value,3) as RoundedValue 

猜猜看,結果是多少?這個例子源自:T-SQL ROUND Function Bug

 

1. 問題起因

    一個老系統,每個月要對系統發生的每筆業務資料進行匯總,產生應收款;但是每個月累計下來,總會與每天的連續累加匯總差(相差幾分錢,或者幾毛錢)。

    同樣的問題還有:倉庫裡面一批按重量計數的物資,每次出庫的時候銷賬;但偶爾會出現:需要出庫的數量,與庫存中現有數量一致,但依然無法出庫的情況。

    問題的根源,就在於資料的儲存類型。系統中,選擇了使用float來儲存資料。但電腦中,是使用近似值來表示float/double的,在這種+/-過程中,誤差不斷地累積,就出現了上述的這些問題。關於浮點數的儲存格式,可以參考我之前的文章:“精確”判斷一個浮點數是否等於0

 

2. Linq to SQL中的Round

     知道了問題的原因,我們就可以針對性地進行處理:對資料進行四捨五入後再進行求和。

    C#中,提供了兩種控制四捨五入的計算方式:MidpointRounding.ToEven和MidpointRounding.AwayFromZero;預設為MidpointRounding.ToEven,即當一個數字是其他兩個數位中間值時,會將其舍入為最接近的偶數。有關舍入方式,可以參考MSDN:“MidpointRounding 枚舉”。

    值得一提的是,裡面對MidpointRounding.AwayFromZero的翻譯有誤,翻譯原文為:“AwayFromZero 當一個數字是其他兩個數位中間值時,會將其舍入為兩個值中絕對值較小的值。”但英文原文為:“When a number is halfway between two others, it is rounded toward the nearest number that is away from zero.” 文檔描述得相當糟糕,不知道是不是我理解能力太差,反正那一坨東西,繞來繞去,還是看裡面舉的例子比較靠譜。

   1: // 3.4 = Math.Round( 3.45, 1)
   2: //-3.4 = Math.Round(-3.45, 1)
   3:  
   4: // 3.4 = Math.Round( 3.45, 1, MidpointRounding.ToEven)
   5: // 3.5 = Math.Round( 3.45, 1, MidpointRounding.AwayFromZero)
   6:  
   7: //-3.4 = Math.Round(-3.45, 1, MidpointRounding.ToEven)
   8: //-3.5 = Math.Round(-3.45, 1, MidpointRounding.AwayFromZero)

 

    L2S中,如果直接用Math.Round(T.Amount, 2, MidpointRounding.AwayFromZero),則解析成T-SQL就是直接調用Round函數;如果用Math.Round(T.Amount, 2, MidpointRounding.ToEven),則會解析為:

   1: (CASE 
   2: WHEN ((([t0].[Amount]) * 2) = round(([t0].[Amount]) * 2, 2)) AND (([t0].[Amount]) <> round([t0].[Amount], 2)) THEN round(([t0].[Amount]) / 2, 2) * 2
   3: ELSE round([t0].[Amount], 2)
   4: END)
    3. 解決方案

3.1 兩次Round

    使用一次Round可以解決小數點取捨問題,但無法處理浮點數精度帶來的誤差,下面是一次Round帶來的結果:

   這是因為,譬如浮點數2053.345在電腦內部儲存的格式可能為2053.2449999999……,一次Round的結果就直接把後面49999…..的給舍掉了。但我們可以使用兩次Round來達到需要的舍入效果:Round(Round(Value, 6), 2)

3.2 Decimal

    處理精度問題,將資料類型定義為Decimal才是王道。

     Decimal在計算及中的儲存格式:Decimal 值長度是128位,由 1 位符號、96 位整數以及比例因素組成,比例因素用作 96 位整數的除數並指定整數的哪一部分為小數。比例因素隱式地定為數字 10 的冪,指數範圍從 0 到 28。因此,Decimal 值的二進位表示形式為:((-2^96 到 2^96) / 10^(0 到 28))。比例因素還保留 Decimal 數字中的所有尾數零。

標籤: Round,Decimal,Linq to Sql

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.