我有一个像这样定义的表:
create table #tbFoo
(bar float)
我正在寻找一种方法来舍入列栏中包含的每个值而不更改总和(已知为整数,或者由于浮点数精度而非常接近整数)。
将每个值舍入到最接近的整数都不起作用(例如:1,5; 1,5将舍入为1; 1或2; 2)
使用多个请求(例如存储原始总和,舍入,计算新总和,并根据需要更新尽可能多的行以返回原始总和),这很容易做到这一点,但这不是一个非常优雅的解决方案
有没有办法使用单个SQL请求执行此操作?
我正在使用SQL Server 2008,因此欢迎利用此特定供应商的解决方案。
编辑:我正在寻找一个请求,尽量减少旧值和新值之间的差异。换句话说,如果向下舍入更大的值,则永远不应该舍入值,反之亦然
答案 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