【问题标题】:Linq to SQL rounding of decimal after division除法后的Linq to SQL四舍五入
【发布时间】:2017-01-11 09:18:45
【问题描述】:

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

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

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

 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 Server 如何存储 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)

第一个查询返回:453.592370000000000000

第二个:0.0004535923

【问题讨论】:

  • 看起来buyUominteger。我敢打赌这与此有关。如果将它与1m 相乘,它会转换为decimal。在这种情况下,我不确定小数与整数相除的效果是什么。
  • @HoneyBadger 不,buyUom 是十进制的。它直接来自 UnitOfMeasure 表中的 Decimal(28,18) 列。将添加截图
  • 你能看看buyUom的值是多少吗?它可能与分配给piUom 时的值不同(相对于有效数字)。
  • @HoneyBadger piUom 的价值?在数据库中,它显示为“1000000.000000000000000000”。我不认为这是一个价值观问题。查看 CalcledAlsoRight 属性。它运行 100% 相同的东西“saleUom / piUom”它得到了正确的答案,但发送到 SQL 的版本得到了错误。
  • 我认为这与重要数字有关。 .NET 中的小数有 28-29 个有效数字,在 sql 中,无论您将它们定义为多少,所以在您的情况下它将是 18。这就是为什么我希望 buyUompiUom 有不同数量的有效数字。当小数被划分时,计算得到的精度和比例。我不知道 .NET 是如何做到这一点的,但我相当有信心您的问题是由此引起的。当您乘以 1m 时,有效数字再次设置为 .NET 默认值 (28-29),这将改变结果的精度和比例。

标签: sql-server linq decimal rounding


【解决方案1】:

这绝对是由于 SQL Server 在计算中处理精度和规模的方式。

如果您将@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 时,相应的小数位数会减小,以防止结果的整数部分被截断。 我认为这就是这里发生的事情。

【讨论】:

  • 谢谢。在玩了一会儿之后,这看起来正是正在发生的事情。
【解决方案2】:

看起来这就是 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多