据我所知,由于 IEEE 754 标准,用户输入的极大数字不会以二进制格式精确存储。当这些不精确存储的值转换回十进制以显示在控制台上时,输出是否应该在不同的操作系统(Windows/Linux)和 CPU 架构之间保持一致?
我之所以问这个问题,是因为我的客户正在使用 JDBC,并且我们注意到 Windows 和 Linux 环境之间的输出值不同。例如,在将数据存储123456.6543214586532653516
到数据库并从 Linux 和 Windows 系统将其打印到控制台后,结果是不同的:
Linux: 123456.65432145866000000000
Windows: 123456.65432145864000000000
我知道由于浮点表示的限制,输入值无法被精确存储,但我不明白为什么控制台输出会根据环境而不同。
为了进一步调查,我在 x86 Windows 和 x86 Linux 环境中用 Python 和 Java 测试了相同的值。具体来说,我将值分配给一个变量并将其直接打印到控制台。在这两种环境中,无论操作系统或虚拟机如何,123456.6543214586532653516
输出都是一致的。123456.65432145866
有人能解释一下是什么原因导致了这种不一致吗?
更具体地说,这个问题源于我的同事从客户那里收到的一个问题。由于这不是我直接收到的问题,而且我和我的同事负责不同的任务,所以我所掌握的信息很遗憾仅限于我在这里分享的内容。我和我的同事都在一家名为 Tibero 的数据库公司工作,该公司支持与 Oracle 类似的查询。客户报告说,在运行相同的 Java 代码时,他们在 Windows 和 Linux 上得到的结果不同。不幸的是,Java 代码属于客户,所以我们无法共享它。但是,客户提到在tbsql(我们公司的类似于 sqlplus 的 CLI 工具)中执行的以下 SQL 查询也会产生相同的差异:
CREATE TABLE convert_test(c1 BINARY_DOUBLE);
INSERT INTO convert_test VALUES(123456.6543214586532653516);
SELECT TO_CHAR(c1, '999999999999999D9999999999999999999', 'NLS_NUMERIC_CHARACTERS = ''.'''') AS formatted_value
FROM convert_test;
不幸的是,我无法在我的环境中重现这个问题。我知道 Tibero 可能不是您熟悉或容易获得的产品,我也理解这可能给您带来一些困难。
为了检查,我还在 Windows 和 Linux 上安装了 PostgreSQL,并执行了以下查询。结果在各个平台上都是一致的,没有差异:
CREATE TABLE convert_test (
c1 DOUBLE PRECISION
);
INSERT INTO convert_test VALUES (123456.6543214586532653516);
SELECT C1 FROM CONVERT_TEST;
c1
--------------------
123456.65432145866
Windows 实现存在缺陷;它错误地将输入或源文本“ 123456.65432145865 32653516”转换为 IEEE-754 binary64 123456.65432145864 1788922250270843505859375,而不是正确的值123456.65432145865 63408374786376953125。(我得出这个结论是基于它是产生观察到的行为的最短错误路径:一个简单的输入转换错误完全解释了输出。)
稍后以小数点后 20 位的格式打印这些内容,将产生“123456.65432145864000000000”和“123456.65432145866000000000”,这是因为Java 的规则是格式化不会直接产生转换为十进制的操作数,而是首先确定一个具有足够小数位的数字,以便唯一地区分一组值中的数字
Double
,然后将该数字四舍五入到请求的长度(如果请求的长度较短)或附加零(如果请求的长度较长)。这很可能是舍入误差,如下面的数学计算所示:
转换整数部分:
转换小数部分:
不需要计算进一步的小数位,因为 17 + 38 = 55 = 53 位有效数字 + 2 位额外位表明应该向上舍入。
合并这两个结果并删除多余的位:
四舍五入并移动基数:
然而,不进行四舍五入:
由于 Java 仅使用足够的十进制数字来唯一区分数字(然后在需要时用零填充),因此这两个数字减少为:
请参阅https://numeral-systems.com/ieee-754-converter/和https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/Formatter.html#dndec