Linq to SQL 除法后的小数舍入

Linq to SQL rounding of decimal after division

我在计算两个度量单位之间的比率时遇到了精度问题。

比率存储在 SQL 服务器数据库中的 UnitOfMeasureTable 中:NumberOfDefaultUnits decimal(28,18)

在我的 linqPad 示例中,我有一个 class 来演示出了什么问题。

 private class Test
 {
   public decimal saleUom { get; set; }
   public decimal piUom { get; set; }

   public decimal RatioRight { get; set; }
   public decimal RatioWrong { get; set; }
   public decimal CalcledAlsoRight { get { return saleUom / piUom; } }
}

void Main() {

var xx = from uom in UnitsOfMeasures.Where(d=> d.Id == 9)
        let buyUom =  uom.NumberOfDefaultUnits
        let sellUom = UnitsOfMeasures.First(d=> d.Id == 13).NumberOfDefaultUnits
        select new Test

{
    saleUom = sellUom,
    piUom = buyUom,
    RatioRight = sellUom / (buyUom * 1m),
    RatioWrong = sellUom / buyUom,
};

xx.First().Dump();

}

结果是:

saleUom 453.592370000000000000 
piUom 1000000.000000000000000000 
RatioRight 0.000453592370000000000 
RatioWrong 0.0004535923 
CalcledAlsoRight 0.00045359237 

花了一段时间才弄清楚必须将除数乘以 1m 才能得到正确的结果。如果将 sellUom 乘以 1m,它会变得更奇怪。 那么结果就是:

RatioRight = (sellUom * 1m) / (buyUom)
RatioRight 0.000453 

我猜这与 SQL 服务器如何存储 Decimal(28,18) 以及 Linq 如何转换除法命令有关。

更新:所有值都是小数

更新 2: 看起来这完全是一个 SQL 舍入的东西。从等式中删除 .Net

 select top 1 uom.NumberOfDefaultUnits
      from UnitOfMeasures uom
      where uom.Id = 13

 select (select top 1 uom.NumberOfDefaultUnits
      from UnitOfMeasures uom
      where uom.Id = 13 ) 
    / 
      (select top 1 uom.NumberOfDefaultUnits
      from UnitOfMeasures uom
      where uom.Id = 9)

第一次查询returns:453.592370000000000000

第二个:0.0004535923

看起来这就是 SQL 处理这些声明的小数大小被彼此划分的方式

 DECLARE @p2 Decimal(28,18) = 1000000
 DECLARE @p3 Decimal(28,18) = 453.59237

 select @p3 / @p2

 DECLARE @p1 Decimal(19,18) = 1  -- This is how Linq sends the * 1m

 select @p3 / (@p2 *@p1)
 select (@p3 *@p1) / @p2

结果:

0.0004535923

0.000453

0.00045359

这肯定是由于 SQL 服务器处理计算精度和规模的方式。

如果您将@mikeymouses 示例更改为对@p1 使用 6 比例,您将获得一致的结果:

DECLARE @p2 Decimal(28,6) = 1000000
DECLARE @p3 Decimal(28,18) = 453.59237

 select @p3 / @p2

 DECLARE @p1 Decimal(19,18) = 1  -- This is how Linq sends the * 1m

 select @p3 / (@p2 *@p1)
 select (@p3 *@p1) / @p2

结果:

0.0004535923700000000000

0.00045359237000

0.00045359237000000000

精度和规模结果记录在 MSDN 上,但关键点是: 对于乘法:

  • 结果精度 = p1 + p2 + 1
  • 结果比例 = s1 + s2

分部:

  • 结果精度 = p1 - s1 + s2 + max(6, s1 + p2 + 1)
  • 结果的比例 = max(6, s1 + p2 + 1)

其中p1和p2是操作数的精度,s1、s2是操作数的小数位数。

需要注意的是,结果精度和小数位数的绝对最大值为38。当结果精度大于38时,相应的小数位数会减小,以防止结果的整数部分被截断。 我认为这就是这里发生的事情。