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时,相应的小数位数会减小,以防止结果的整数部分被截断。
我认为这就是这里发生的事情。
我在计算两个度量单位之间的比率时遇到了精度问题。
比率存储在 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时,相应的小数位数会减小,以防止结果的整数部分被截断。 我认为这就是这里发生的事情。