java 浮点数运算_对于同样的浮点数运算为何 Java 与 C 的结果不相同?

@bombless 在问题的评论里写得没错。IEEE 754最重要的(大家基本上遵守的)是数据的格式。虽然也有算法上的指引(例如有各种rounding mode),但实际上大家实现得不一定那么严格。

C/C++和Java在浮点数运算上规定都比较松。

Java语言上有“FP-strict”的概念。所有浮点数常量表达式和带有strictfp修饰的代码是FP-strict的,而其它浮点数运算默认不是FP-strict的。规定如下:

Java Language Specification, Chapter 15. Expressions, 15.4. FP-strict Expressions

Within an FP-strict expression, all intermediate values must be elements of the float value set or the double value set, implying that the results of all FP-strict expressions must be those predicted by IEEE 754 arithmetic on operands represented using single and double formats.

Within an expression that is not FP-strict, some leeway is granted for an implementation to use an extended exponent range to represent intermediate results; the net effect, roughly speaking, is that a calculation might produce "the correct answer" in situations where exclusive use of the float value set or double value set might result in overflow or underflow.我加黑的部分说的就是默认的、非FP-strict时的运算规定。可见其并不要求严格遵循IEEE 754对单精度和双精度的运算规定。现实来说就是为了容许像x86/x87这样的组合带来的中间运算结果的精度比IEEE 754所规定的高的情况。

C11对浮点类型的规定如下:

The C floating types match the IEC 60559 formats as follows:

— The float type matches the IEC 60559 single format.

— The double type matches the IEC 60559 double format.

— The long double type matches an IEC 60559 extended format,357) else a non-IEC 60559 extended format, else the IEC 60559 double format.

Any non-IEC 60559 extended format used for the long double type shall have more precision than IEC 60559 double and at least the range of IEC 60559 double.(IEC 60559 等价于 IEEE 754)

但实际的C语言实现通常会允许程序员用精度换速度。例如GCC就有很多参数可以指定各种规则要不要遵守:https://gcc.gnu.org/wiki/FloatingPointMath

然后C++嗯,以MSVC为例,它也允许程序员指定运算精度与速度间的取舍:Microsoft Visual C++ Floating-Point Optimization

在x86/x87环境中进行浮点数运算,如果在x87用默认精度(扩展双精度,也就是80-bit的浮点数格式)上连续做超过1次运算而不对中间结果做调整,那它的精度就会比IEEE 754的双精度要高,运算结果自然就不完全一样了。这只是一个例子,能让浮点数运算与“预期”不一样的因素挺多,例如换换顺序、折叠一下常量啥的…

我们在实现HotSpot JVM的时候在非FP-strict的地方基本上只要保证解释器跟JIT编译器对一些特定Math浮点数方法返回的结果足够接近就好了…

-----------------------

来再geek一点吧。

Java语言规范说浮点数字面量转换到浮点类型数值的算法由 java.lang.Double.valueOf(String) 方法的文档规定。而后者指定使用IEEE 754的round-to-nearest模式来转换。

Java里,

0.01d + 0.05d是一个常量表达式,由语言规范规定必须做常量折叠。这个常量折叠是FP-strict的。

根据规范,把字面量转换为Java认知的double,

0.01d:

十六进制:

3f847ae147ae147b二进制:

0 01111111000 0100011110101110000101000111101011100001010001111011十进制:

0.01000000000000000020816681711721685132943093776702880859375

0.05d:

十六进制:

3fa999999999999a二进制:

0 01111111010 1001100110011001100110011001100110011001100110011010十进制:

0.05000000000000000277555756156289135105907917022705078125

(懒人提示:要看一个Java double的准确十进制表示,只要

new BigDecimal(x).toString()就好了…)

可见这两个数字在double格式都无法准确表示,而只能用近似值。

这两个浮点数相加的结果是:

二进制:

0 01111111010 1110101110000101000111101011100001010001111010111001十进制:

0.060000000000000004718447854656915296800434589385986328125

然后Java的formatter(java.text.DecimalFormat)在把这个double转换为字符串的时候,它并不追求把数字的真实值准确转换出来,而是更追求转换速度,所以会看情况round一下…所以楼主会看到类似 6.0000000000000005E-2 这样的字符串表示。

题主可以试试看不写0.01d+0.05d,而直接写0.06d然后试试把它输出成字符串看看。你会看到它其实也不是精确值,而是

0.059999999999999997779553950749686919152736663818359375,而Java的DecimalFormat即便用

"0.0000000000000000000000000000000000000000000000E0"的格式也会把它格式化成

6.0000000000000000000000000000000000000000000000E-2嗯挺迷惑人的。

而楼主用的C的环境多半在格式化字符串的时候用了别的取舍所以多看到了一位…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值