sql - 如何处理浮点数的精度问题?
问题描述
我正在使用 Firebird 3.0.4(在 Windows 和 Linux 中)并且我有以下过程清楚地展示了我的浮点数问题,并且还展示了一种可能的解决方法:
create or alter procedure test_float returns (res double precision,
res1 double precision,
res2 double precision)
as
declare variable z1 double precision;
declare variable z2 double precision;
declare variable z3 double precision;
begin
z1=15;
z2=1.1;
z3=0.49;
res=z1*z2*z3; /* one expects res to be 8.085, but internally, inside the procedure
it is represented as 8.084999999999.
The procedure-internal representation is repaired when then
res is sent to the output of the procedure, but the procedure-internal
representation (which is worng) impacts the further calculations */
res1=round(res, 2);
res2=round(round(res, 8), 2);
suspend;
end
On 可以看到程序的结果:
select proc.res, proc.res1, proc.res2
from test_float proc
结果是
RES RES1 RES2
8,085 8,08 8,09
但是可以预期 RES2 应该是 8.09。
可以清楚地看到 res 的内部表示包含 8.0849999(例如可以将 res 分配给异常消息,然后引发此异常),它在输出期间被修复,但在进一步使用该变量时导致计算失败计算。
RES2
演示修复:我可以随时申请ROUND(..., 8)
修复内部表示。我已准备好采用此解决方案,但我的问题是 - 它是可接受的解决方法(当外部 ROUND 严格小于 5 位小数时)还是有更好的解决方法。
我的所有测试都通过这种解决方法通过,但感觉很糟糕。
当然,我知道每个程序员都应该知道的关于浮点数的最低限度(有一篇关于它的文章),而且我知道不应该使用 double 进行业务计算。
解决方案
这是使用浮点数计算的固有问题,并非 Firebird 特有的。问题是15 * 1.1 * 0.49
使用双精度数的计算并不完全是 8.085。事实上,如果你这样做8.085 - RES
,你会得到一个(大约)的值1.776356839400251e-015
(尽管你的客户可能只是将它显示为0.00000000
)。
你会在不同的语言中得到类似的结果。例如,在 Java 中
DecimalFormat df = new DecimalFormat("#.00");
df.format(15 * 1.1 * 0.49);
也将8.08
出于完全相同的原因产生。
此外,如果您更改操作顺序,您会得到不同的结果。例如 using15 * 0.49 * 1.1
将产生8.085
并舍入到8.09
,因此实际结果将符合您的期望。
鉴于round
本身也返回双精度,这并不是在 SQL 代码中处理此问题的好方法,因为具有更多小数位数的舍入值可能仍会产生略低于您预期的值,因为如何浮点数有效,因此即使您的客户端中的演示“看起来”正确,某些数字的双轮仍可能失败。
如果您纯粹出于演示目的而希望这样做,那么在您的前端执行此操作可能会更好,但您也可以尝试添加一个小值和强制转换为 的技巧decimal
,例如:
cast(RES + 1e-10 as decimal(18,2))
然而,这仍然存在舍入问题,因为无法区分真正为 8.08499999999 的值(应向下舍入为 8.08)和计算结果恰好是浮点数为 8.08499999999 的值,而它将是 8.085精确数字(因此需要四舍五入到 8.09)。
以类似的方式,您可以尝试使用双重转换为decimal
(例如cast(cast(res as decimal(18,3)) as decimal(18,2))
),或转换decimal
然后四舍五入(例如round(cast(res as decimal(18,3)), 2)
。这将比双重四舍五入更一致,因为第一次转换将转换为精确的数字,但这又一次与上述类似的缺点。
虽然你不想听到这个答案,但如果你想要精确的数字语义,你不应该使用浮点类型。
推荐阅读
- rcpp - Rcpp:获取临时对象类型代理的地址,从 CharacterVector 中获取子向量
- sql - 迄今为止的标准 SQL 字符串
- javascript - 如果类列表包含多个特定类
- clojure - 从 src 目录提供图像文件 - 404 错误
- php - 如何从需要燃料的汽车阵列中获得最大的等待时间
- c# - 防止用户在 C# 中向字符串变量输入整数
- javascript - 未捕获的类型错误:函数不是函数
- javascript - Chrome 浏览器 GCM - TOO_MANY_REGISTRATIONS - 如何取消注册/清除所有已注册的应用程序 ID (registration.pushManager.subscribe)
- c# - 拆分所有列表框元素并将它们全部添加到新的字符串数组中
- gmail - Gmail 推送通知和更新手表