【问题标题】:Oracle SQL rounding misbehaviorOracle SQL 舍入错误行为
【发布时间】:2014-08-05 08:50:54
【问题描述】:

我在使用 Oracle SQL 时遇到了 binary_double 舍入的奇怪行为。 binary_double 值应该根据documentation 四舍五入half even,但是当使用以下查询对此进行测试时,似乎存在一些不一致之处。 以下所有查询应分别给出相同的最后一位数字,即 0.x00008 和 0.x00006(四舍五入为 6 位)或 0.x0008 和 0.x0006(四舍五入为 5 位) x in (0,1,2,3,4,5,6,7,8,9)。 问题是他们没有。任何有助于理解为什么舍入结果取​​决于分隔点后的第一个数字和/或原始数字中的位数的任何帮助。

select 1,(round( cast (0.0000075 as binary_double ) ,6)), (round( cast (0.0000065 as binary_double ) ,6)) from dual
  union
  select 2,(round( cast (0.1000075 as binary_double ) ,6)), (round( cast (0.1000065 as binary_double ) ,6)) from dual
  union
  select 3,(round( cast (0.2000075 as binary_double ) ,6)), (round( cast (0.2000065 as binary_double ) ,6)) from dual
  union
  select 4,(round( cast (0.3000075 as binary_double ) ,6)), (round( cast (0.3000065 as binary_double ) ,6)) from dual
  union
  select 5,(round( cast (0.4000075 as binary_double ) ,6)), (round( cast (0.4000065 as binary_double ) ,6)) from dual
  union
  select 6,(round( cast (0.5000075 as binary_double ) ,6)), (round( cast (0.5000065 as binary_double ) ,6)) from dual
  union
  select 7,(round( cast (0.6000075 as binary_double ) ,6)), (round( cast (0.6000065 as binary_double ) ,6)) from dual
  union
  select 8,(round( cast (0.7000075 as binary_double ) ,6)), (round( cast (0.7000065 as binary_double ) ,6)) from dual
  union
  select 9,(round( cast (0.8000075 as binary_double ) ,6)), (round( cast (0.8000065 as binary_double ) ,6)) from dual
  union
  select 10,(round( cast (0.9000075 as binary_double ) ,6)), (round( cast (0.9000065 as binary_double ) ,6)) from dual
  union
  select 11,(round( cast (0.000075 as binary_double ) ,5)), (round( cast (0.000065 as binary_double ) ,5)) from dual
  union
  select 12,(round( cast (0.100075 as binary_double ) ,5)), (round( cast (0.100065 as binary_double ) ,5)) from dual
  union
  select 13,(round( cast (0.200075 as binary_double ) ,5)), (round( cast (0.200065 as binary_double ) ,5)) from dual
  union
  select 14,(round( cast (0.300075 as binary_double ) ,5)), (round( cast (0.300065 as binary_double ) ,5)) from dual
  union
  select 15,(round( cast (0.400075 as binary_double ) ,5)), (round( cast (0.400065 as binary_double ) ,5)) from dual
  union
  select 16,(round( cast (0.500075 as binary_double ) ,5)), (round( cast (0.500065 as binary_double ) ,5)) from dual
  union
  select 17,(round( cast (0.600075 as binary_double ) ,5)), (round( cast (0.600065 as binary_double ) ,5)) from dual
  union
  select 18,(round( cast (0.700075 as binary_double ) ,5)), (round( cast (0.700065 as binary_double ) ,5)) from dual
  union
  select 19,(round( cast (0.800075 as binary_double ) ,5)), (round( cast (0.800065 as binary_double ) ,5)) from dual
  union
  select 20,(round( cast (0.900075 as binary_double ) ,5)), (round( cast (0.900065 as binary_double ) ,5)) from dual;

底线是: 为什么在以下查询中,两个值之间存在差异:

SELECT (round( CAST (0.0000065 AS BINARY_DOUBLE ) ,6)), (round( cast (0.1000065 as binary_double ) ,6)) FROM dual;

按照@zerkms 的建议,我将convert 的数字转换为二进制格式,我得到:

0.0000065 -> 6.49999999999999959998360846147E-6
0.1000065 -> 1.00006499999999998173905169097E-1

查询将其四舍五入为 6 位数。令人惊讶的是,对我来说,我看到四舍五入的结果是:

0.0000065 -> 0.000006 (execute the query above to see this)
0.1000065 -> 0.100007 (execute the query above to see this)

这是为什么呢?我可以理解,如果我尝试四舍五入到 >12 位,二进制表示中的数字系列开始不同,但是为什么在这么早的阶段就可以看到差异?

【问题讨论】:

    标签: sql oracle casting rounding


    【解决方案1】:

    让我们看一下第一个示例,因为其他示例非常相似:

    双精度 IEEE 754 中的 0.0000075 表示为 7.50000000000000019000643072808E-6

    0.0000065 显示为 6.49999999999999959998360846147E-6

    当您将两者都取 6 时 - 前者变为 8e-6,后者变为 6e-6

    没有“一致”的行为,因为不同的数字被分解为 2 的除数。

    因此,即使您执行SELECT 0.0000065 FROM DUAL 并看到0.0000065 结果 - 它不是在内部以二进制形式表示的方式,它已经“损坏”并且比该数字小一小部分。然后在输出格式化期间为您四舍五入。

    双 IEEE 754 提供15-16 significant digits。因此,出于输出目的,它们变为:7.500000000000000e-66.499999999999999e-6,四舍五入为 6.5e-6

    UPD

    6.49999999999999959998360846147E-6 == 0.00000649999999999999959998360846147。如果你将它四舍五入 - 它等于0.000006,因为它后面是4,它小于5

    1.00006499999999998173905169097E-1 == 0.100006499999999998173905169097 被 6 舍入为0.100006,因为下一位是4,小于5。我看到了与实际结果的差异。老实说,我在这里没有很好的解释。我怀疑这是一个 oracle 问题,因为:

    UPD 2

    在与来自 Skype 聊天的同事进行更多研究后,我得到了一个很好的例子,结果取决于所选的舍入模式:

    flock.core> (import '[org.apache.commons.math3.util Precision])
    
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_CEILING)
    0.100007
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_DOWN)
    0.100006
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_UP)
    0.100007
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_HALF_DOWN)
    0.100006
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_HALF_EVEN)
    0.100006
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_HALF_UP)
    0.100007
    flock.core> (Precision/round (Double. 0.1000065) 6 BigDecimal/ROUND_FLOOR)
    0.100006
    

    结论

    在这种情况下没有“正确”或“不正确”的结果,它们都是正确的并且很大程度上取决于实现(+ 执行算术运算时使用的选项)。

    参考资料:

    【讨论】:

    • 谢谢您,到达那里,但仍然...我根据您的评论编辑了问题,请看一下。我声称仍然有问题。
    • @Patrick Hofman:请稍等片刻。最后一个0.1000065 结果不符合预期:-(
    • 没错,这就是我的观点。我运行了一个 Java 代码,其中舍入也按预期工作,然后遇到了针对 Oracle 的差异问题。问题仍未解决...非常感谢您的努力!
    • @Simon Righley:在 java 中没有“真正的”舍入。您混合了多个运算(乘法和除法),这会影响精度。幸运的是它会影响到“预期”的一面,但不应该被视为这种情况。
    • @Simon Righley:根据该文档,它必须是0.100006。在 Go 和 python 上添加了示例,这些示例也返回了预期值。所以我怀疑这只是 Oracle corp 的实现,它都会影响 jvm 和 oracle dbms。
    【解决方案2】:

    您最好使用 DECIMAL 数据类型以避免舍入问题。

    更多信息在这里:http://docs.oracle.com/javadb/10.6.2.1/ref/rrefsqlj15260.html

    试试这个:

    select 1,round(cast (0.0000075 as decimal(15,7)),6), round(cast (0.0000065 as decimal(15,7)),6) from dual;
    

    由于我没有安装 Oracle 数据库,我无法对其进行测试,但它应该可以工作。

    一个重要的说明:如果小数的比例小于实际数字,超过小数将被截断。因此,您可能希望转换为十进制 (17,8) 以获得更高的安全性。

    【讨论】:

    • 谢谢,但是链接失效了,能补一下吗?
    【解决方案3】:

    这是因为binary_double 是一种浮点数据类型,并不总是(或总是不)准确。

    请参阅 Oracle 的 this related article 了解浮点数据类型。

    【讨论】:

    • 好的,但在 Java、C 等中始终不是(准确)任何浮点数据类型,而且我仍然可以强制执行一致的舍入规则,而对于上面的示例,看起来像在 Oracle 中根本没有舍入模式。还是我错过了什么?
    • @Simon Righley:浮点数(单精度)不能保证超过 6 个有效数字的精度。它是设计使然 (IEEE 754),它在任何正确的实现(包括 Java 和诸如此类)中的行为方式都相同。
    • 0.100075 不能用二进制准确表示,所以无论你有多少精度,二进制浮点值总是会比 0.100075 大或小一点。
    • @zerkms:非常感谢您的 pingback。尚未阅读您的最新更新,但仍被迷住了。对不起,但不能两次投票给你;)
    • @Patrick Hofman:我希望我能给自己投票:-D 在调查过程中学到了很多
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多