我当前正在使用 Spark SQL 提取数据集,需要在最后一行附加其统计信息。
-- data
SELECT account, balance as amount FROM table
UNION ALL
SELECT account, interest as amount FROM table;
-- footer
SELECT SUM(balance + interest) FROM table;
-- 11837546207.43
但是,另一个团队的下游验证脚本使用此命令:
cut -d"|" -f2 file.csv | awk '{sum+=$0} END {print sum}' OFMT='%f'
# 11837546207.140182
我知道这种差异是由计算机中浮点运算的精度限制引起的,其中小数点后两位的数字有时可以在内部表示为最多小数点后六位。 我的问题是 - 如何调整我的 SQL 以反映这种精度限制,以便输出可以通过验证过程?
我尝试将余额转换为
DECIMAL(25, 6)
,为利息做同样的事情,然后再次转换总和。然而,结果只是 11837546207.430000,它只是添加了更多的尾随零。
有人可以帮忙吗?
这里应用基本的软件工程原理:错误是与规范的偏差。牢记这一点,我们考虑这个问题:
如何调整我的 SQL 以反映这种精度限制,以便输出可以通过验证过程?
为什么我们要调整SQL代码?从表面上看,SQL 代码似乎已经产生了所需的结果,而“验证”1 脚本正在计算错误的结果:
balance
和interest
的使用表明这是一种货币计算,而货币计算几乎总是以十进制算术中的小数金额进行。您应该评估这个假设。手动或以更高的精度添加值,然后查看哪个结果是正确的。此外,决定代码的规范是什么:它是否应该产生精确的结果,即恰好是所添加的数字之和的唯一数字,或者是否应该产生一些受舍入和有限精度影响的结果?
如果 SQL 代码产生了所需的结果,则应保持原样,并且应修复“验证”脚本。理想情况下,可以通过将验证脚本更改为使用十进制算术或其他适合精确再现所需结果的算术来修复验证脚本。但是,可以通过更改它以接受其计算结果的某个公差范围内的结果来修复它,计算公差以允许测试脚本内部使用的不合适算术中的舍入误差。
更改正确结果以匹配“验证”脚本将是一个错误。
如果不知何故,“验证”脚本结果正确,而 SQL 代码错误,并且您想更改 SQL 代码以产生与“验证”代码相同的结果,那么您需要更改 SQL 代码以使用与“验证”代码相同的算术(例如,binary64),包括以相同的顺序执行相同的操作。 (例如,加法在有限精度算术中不可交换,因此以不同顺序添加数字可能会产生不同的结果。)
…计算机中浮点运算的精度限制,其中小数点后 2 位的数字有时可以在内部表示为最多 6 位小数…
这不是对浮点运算的一个很好的描述。有人说 IEEE-754 二进制 32 格式的精度约为六位数字,但这是一个“不好的说法”。但这并不适用于此;该格式无法生成结果 11837546207.140182。 (它能得到的最接近的是 11837546496。)该脚本可能使用 binary64 格式,也称为“双精度”。 脚注
1我在引号中使用“验证”,因为这看起来像是一个测试脚本,而不是验证脚本。很少有程序经过实际验证,因为这需要数学证明或详尽的测试。更有可能的是,这是一个测试脚本,试图查找程序中的错误。一个区别是发现 bug 是一个积极的结果;而找到 bug 则是一个积极的结果。编写测试的动机应该是发现错误,而不是验证程序是否正确。另外,如果一个程序通过了测试脚本,这意味着我们没有发现任何错误,而不是程序中没有错误。