编辑:我知道浮点算法是不精确的。而且算术甚至不是我的问题。加法得到了我预期的结果。 8099.99975f
不。
所以我有这个小程序:
public class Test {
public static void main(String[] args) {
System.out.println(8099.99975f); // 8099.9995
System.out.println(8099.9995f + 0.00025f); // 8100.0
System.out.println(8100f == 8099.99975f); // false
System.out.println(8099.9995f + 0.00025f == 8099.99975f); // false
// I know comparing floats with == can be troublesome
// but here they really should be equal in every bit.
}
}
我编写该代码是为了检查将其写为IEEE 754单精度浮点数时是否将
8099.99975
舍入为8100
。令我惊讶的是,Java在以浮点文字(8099.9995
)编写时将其转换为8099.99975f
。我再次检查了计算结果和IEEE标准,但没有发现任何错误。 8100
与8099.99975
的距离与8099.9995
一样远,但是8100
的最后一位是0
,这应该使它正确。因此,我检查了Java语言规范,看是否错过了一些东西。快速搜索后,我发现了两件事:
Java编程语言要求浮点算术的行为就像每个浮点运算符将其浮点结果四舍五入到结果精度一样。不精确的结果必须四舍五入到最接近无限精确结果的可表示值;如果两个最接近的可表示值相等地接近,则选择其最低有效位为零的那个。
当将浮点值转换为整数时,Java编程语言使用朝零取整。
我在这里注意到,关于浮动字面量没有任何提及。因此,我认为float文字可能只是double,类似于将float转换为int时,将其转换为float时将舍入为零。这可以解释为什么
8099.99975f
舍入为零。我写了上面可以看到的小程序来检查我的理论,确实发现,当添加两个会导致
8100
的float文字时,会计算出正确的float。 (请注意,这里8099.9995
和0.00025
可以精确地表示为单个浮点数,因此没有舍入可能导致不同的结果)这使我感到困惑,因为对我来说,浮点数文字和计算出的浮点数表现得并不合理不同,所以我在语言规范中进行了更多研究,发现了这一点:如果浮点文字后缀为ASCII字母F或f [...],则其类型为float。浮点类型的元素是可以使用IEEE 754 32位单精度二进制浮点格式表示的那些值。
最终指出应根据IEEE标准对文字进行四舍五入,在本例中为
8100
。那么为什么是8099.9995
? 最佳答案
要实现的关键点是,浮点数的值可以用两种不同的方法求出,但通常不相等。
浮点数中的位具有确切的二进制表示形式。
浮点数有一个“十进制显示值”,即与其他任何数字相比,小数位数最少的数字更接近该浮点数。
要了解差异,请考虑指数为10001011且有效值为1.11111010001111111111111的数字。这是8099.99951171875的确切二进制表示。但是十进制值8099.9995的小数位数较少,并且与任何其他浮点数相比,它更接近于此浮点数。因此,8099.9995是当您打印该数字时将显示的值。
请注意,此特定的浮点数是8100之后的第二个最低浮点数。
现在考虑8099.99975。与8100相比,它更接近8099.99951171875。因此,为了用单精度浮点表示,Java将选择浮点数,它是8099.99951171875的精确二进制表示形式。如果尝试打印,则会看到8099.9995。
最后,当您在单精度浮点中执行8099.9995 + 0.00025时,所涉及的数字是8099.99951171875和0.0002499999827705323696136474609375的精确二进制表示形式。但是因为后者略大于1/2 ^ 12,所以加法的结果将比8099.99951171875的结果更接近8100,因此将四舍五入而不是最后取整,使其成为8100。