Linq Sum() 精度

Posted

技术标签:

【中文标题】Linq Sum() 精度【英文标题】:Linq Sum() precision 【发布时间】:2014-06-21 21:40:16 【问题描述】:

在我的项目中,我经常使用Linq's Sum()。它由 NHibernate on mysql 提供支持。在我的 Session Factory 中,我明确要求 NHibernate 处理 decimals 时的小数点后 8 位:

public class DecimalsConvention : IPropertyConvention

    public void Apply(IPropertyInstance instance)
    
        if (instance.Type.GetUnderlyingSystemType() == typeof(decimal))
        
            instance.Scale(8);
            instance.Precision(20);
        
    

但是,我发现.Sum() 将数字四舍五入到小数点后 5 位:

var opasSum = opasForThisIp.Sum(x => x.Amount); // Amount is a decimal

在上面的语句中opaSum等于2.46914而应该是2.46913578(直接在MySQL上计算)。 opasForThisIp 的类型为 IQueryable<OutgoingPaymentAssembly>

当涉及到decimals 时,我需要所有 Linq 计算来处理 8 位小数。

关于如何解决这个问题的任何想法?

编辑 1:我发现 var opasSum = Enumerable.Sum(opasForThisIp, opa => opa.Amount); 可以产生正确的结果,但问题仍然存在,为什么 .Sum() 会四舍五入结果,我们该如何解决?

编辑2:生成的SQL好像有问题:

select cast(sum(outgoingpa0_.Amount) as DECIMAL(19,5)) as col_0_0_ 
from `OutgoingPaymentAssembly` outgoingpa0_ 
where outgoingpa0_.IncomingPayment_id=?p0 
and (outgoingpa0_.OutgoingPaymentTransaction_id is not null);
?p0 = 24 [Type: UInt64 (0)]

编辑 3var opasSum = opasForThisIp.ToList().Sum(x => x.Amount); 也会产生正确的结果。

编辑 4:将 IQueryable<OutgoingPaymentAssembly> 转换为 IList<OutgoingPaymentAssembly> 使原始查询:var opasSum = opasForThisIp.Sum(x => x.Amount); 起作用。

【问题讨论】:

在运行var opasSum = opasForThisIp.Sum(x => x.Amount);时,有没有检查生成的SQL语句? @DominicKexel 说得好,SQL语句确实有问题,看我的编辑。 我不明白。 .Sum()确实是Enumerable.Sum()的扩展方法,为什么会产生不同的SQL语句? opasForThisIp 是什么类型的? 如果您将 IQueryable 强制列在列表中,我很想知道生成的 SQL 会发生什么。 var opasSum = opasForThisIp.ToList().Sum(x => x.Amount);看起来应该和调用 Enumerable.Sum() 的效果一样,但我不能肯定。 【参考方案1】:

x.Amount 正在从“LINQ-to-SQL”转换转换为低精度最小类型,因为您的集合是 IQueryable。

有几种解决方法,其中最简单的是将集合的类型更改为 IList,或在集合上调用 ToList(),强制 linq 查询作为 LINQ-to-Objects 运行。

var opasSum = opasForThisIp.ToList().Sum(x => x.Amount);

注意: 如果您不想因离开 IQueryable 而失去延迟执行,您可以尝试在 linq 查询中将 Amount 转换为小数。

来自MSDN decimal and numeric (Transact-SQL):

在 Transact-SQL 语句中,带小数点的常量是 使用最小值自动转换为数值数据值 精度和规模是必要的。例如,常数 12.345 是 转换为精度为 5、小数位数为 3 的数值。

编辑(包括对不同 .NET 集合类型的详细解释:

取自this SO question.的回答

IQueryable 旨在允许查询提供程序(例如, ORM(如 LINQ to SQL 或实体框架)来使用表达式 包含在查询中以将请求转换为另一种格式。在 换句话说,LINQ-to-SQL 查看实体的属性 你正在使用与你正在做的比较,实际上 创建一个 SQL 语句来表达(希望)一个等效的请求。

IEnumerable 比 IQueryable 更通用(尽管所有 IQueryable 的实例实现 IEnumerable) 并且只定义 一个序列。但是,在 在其上定义一些查询类型运算符的可枚举类 接口并使用普通代码来评估这些条件。

List 只是一种输出格式,虽然它实现了 IEnumerable,与查询没有直接关系。

换句话说,当您使用 IQueryable 时,您正在定义和 被翻译成别的东西的表达。虽然 您正在编写代码,该代码永远不会被执行,它只会得到 检查并变成其他东西,例如实际的 SQL 查询。 正因为如此,只有某些东西在这些范围内是有效的 表达式。例如,你不能调用一个普通函数 您从这些表达式中定义,因为 LINQ-to-SQL 没有 知道如何将您的调用转换为 SQL 语句。其中大部分 不幸的是,限制仅在运行时评估。

当您使用 IEnumerable 进行查询时,您正在使用 LINQ-to-Objects,这意味着您正在编写实际的代码 用于评估您的查询或转换结果,所以有 一般来说,您可以做的事情没有任何限制。你可以打电话 这些表达式中的其他功能。

【讨论】:

此外,您可能会幸运地检查出 SqlFunctions 方法。我自己对他们了解不多。 msdn.microsoft.com/en-us/library/…

以上是关于Linq Sum() 精度的主要内容,如果未能解决你的问题,请参考以下文章

Lambda表达式mapToDouble.sum精度问题

练习4-3 求给定精度的简单交错序列部分和(15 分)

如何计算 C++ 中双精度向量的累积和?

总结元素时numpy float32的精度值变化[重复]

[PTA]实验4-1-8 求给定精度的简单交错序列部分和

使用 LINQ 在 C# 中查找数字数组的累积和