如何在不更改总和的情况下对单个SQL请求中的列进行舍入?

时间:2009-09-17 10:17:13

标签: sql sql-server algorithm sql-server-2008

我有一个像这样定义的表:

create table #tbFoo
(bar float)

我正在寻找一种方法来舍入列栏中包含的每个值而不更改总和(已知为整数,或者由于浮点数精度而非常接近整数)。

将每个值舍入到最接近的整数都不起作用(例如:1,5; 1,5将舍入为1; 1或2; 2)

使用多个请求(例如存储原始总和,舍入,计算新总和,并根据需要更新尽可能多的行以返回原始总和),这很容易做到这一点,但这不是一个非常优雅的解决方案

有没有办法使用单个SQL请求执行此操作?


我正在使用SQL Server 2008,因此欢迎利用此特定供应商的解决方案。


编辑:我正在寻找一个请求,尽量减少旧值和新值之间的差异。换句话说,如果向下舍入更大的值,则永远不应该舍入值,反之亦然

3 个答案:

答案 0 :(得分:3)

<强>更新

请参阅我的博客文章中详细解释的此解决方案:


您需要为每个值保留累积偏移量:

1.2   (1 + 0.0)  ~ 1    1   1.2   +0.2
1.2   (1 + 0.2)  ~ 1    2   2.4   +0.4
1.2   (1 + 0.4)  ~ 1    3   3.6   +0.6
1.2   (1 + 0.6)  ~ 2    5   4.8   -0.2
1.2   (1 - 0.2)  ~ 1    6   6.0   0.0

这可以在MySQL中轻松完成,但在SQL Server中,您必须编写游标或使用累积子选择(效率较低)。

<强>更新

下面的查询选择值之和与向下舍入到最接近的较小整数之间的差值。

这为我们提供了应该向上舍入的数字(N)。

然后我们按小数部分(最接近天花板的部分)排序值,然后将第一个N向上舍入,其他部分向下舍入。

SELECT  value,
        FLOOR(value) + CASE WHEN ROW_NUMBER() OVER (ORDER BY value - FLOOR(value) DESC) <= cs THEN 1 ELSE 0 END AS nvalue
FROM    (
        SELECT  cs, value
        FROM    (
                SELECT  SUM(value) - SUM(FLOOR(value)) AS cs
                FROM    @mytable
                ) c
        CROSS JOIN
                @mytable
        ) q

以下是测试数据的脚本:

SET NOCOUNT ON
GO
SELECT  RAND(0.20090917)
DECLARE @mytable TABLE (value FLOAT NOT NULL)
DECLARE @cnt INT;
SET @cnt = 0;
WHILE @cnt < 100
BEGIN
        INSERT
        INTO    @mytable
        VALUES  (FLOOR(RAND() * 100) / 10)
        SET @cnt = @cnt + 1
END

INSERT
INTO    @mytable
SELECT  600 - SUM(value)
FROM    @mytable

答案 1 :(得分:1)

如果您有一个n个值的列表,其元素仅在整数值(+ -0.5)内是准确的,那么这些元素的任何总和都将具有累积误差或+ - (n * 0.5)。如果你的列表中有6个元素应该加起来一些数字,那么你最糟糕的情况就是如果你只是添加整数值就会减去3。

如果你找到一种方法将10.2显示为11以使总和工作,你已经将该元素的精度从+ -0.5更改为+ -0.8,这在查看整数时是违反直觉的?

要考虑的一个可能的解决方案是仅在显示期间对您的数字进行舍入(使用输出上的某些格式字符串),而不是在检索阶段。每个数字都尽可能接近实际值,但总和也会更正确。

示例:如果您有3个值,每个值为1/3,显示为整数百分比,那么您应该显示33,33和33.要做任何其他事情,要创建大于+ -0.5的误差范围对于任何个人价值。您的总数仍应显示为100%,因为这是最佳值(与使用已舍入值的总和相反)

另外,请注意,通过使用浮点数,您已经引入了对精度的限制,因为您无法准确表示0.1。有关更多信息,请阅读 What Every Computer Scientist Should Know About Floating-Point Arithmetic

答案 2 :(得分:0)

首先得到舍入和与实际总和之间的差异以及记录数:

declare @Sum float, @RoundedSum float, @Cnt int

select @Sum = sum(bar), @RoundedSum = sum(round(bar)), @Cnt = count(*)
from #tbFoo

然后在四舍五入之前将差异平均分配给所有值:

declare @Offset float

set @Offset = (@Sum - @RoundedSum) / @Cnt

select bar = round(bar + @Offset)
from #tbFoo