为什么“ 2.0-1.1”和“ 2.0F-1.1F”产生不同的结果?

我正在编写一个比较Doublefloat值的代码:

class Demo {
    public static void main(String[] args) {
        System.out.println(2.0 - 1.1);            // 0.8999999999999999
        System.out.println(2.0 - 1.1 == 0.9);     // false

        System.out.println(2.0F - 1.1F);          // 0.9
        System.out.println(2.0F - 1.1F == 0.9F);  // true
        System.out.println(2.0F - 1.1F == 0.9);   // false
    }
}

输出如下:

0.8999999999999999
false
0.9
true
false

我相信Double的值比float可以节省更多的精度。

请对此进行解释,看起来float的值不是精度损失,而是double的精度下降?


编辑: @goodvibration我知道不能完全以任何一种计算机语言保存0.9,我只是很困惑java如何详细地使用它,为什么2.0F - 1.1F == 0.9F,但是2.0 - 1.1 != 0.9,可能会发现另一个有趣的问题。帮助:

class Demo {
    public static void main(String[] args) {
        System.out.println(2.0 - 0.9);            // 1.1
        System.out.println(2.0 - 0.9 == 1.1);     // true

        System.out.println(2.0F - 0.9F);          // 1.1
        System.out.println(2.0F - 0.9F == 1.1F);  // true
        System.out.println(2.0F - 0.9F == 1.1);   // false
    }
}

我知道我不能指望浮动精度或双精度,只是..不能弄清楚它让我发疯了,这背后的真正目的是什么?为什么选择2.0 - 0.9 == 1.1但选择2.0 - 1.1 != 0.9

feng0905 回答:为什么“ 2.0-1.1”和“ 2.0F-1.1F”产生不同的结果?

流行测验:以小数表示1/3。

答案:您不能;不能。不完全是

计算机以二进制计数。还有更多数字“无法完全代表”。就像在十进制问题中一样,如果您只需要写一小块纸,则可以简单地使用0.3333333并将其命名为一天,然后得到一个非常接近的数字与1 / 3相同,但并不完全相同,因此计算机也代表分数。

或者,这样考虑:浮点数占用32位;双占64位。32位值只能表示2 ^ 32(约40亿)个不同的数字。但是,即使在0到1之间,也有无限数量的数字。因此,鉴于最多有2 ^ 32个具体的具体数字可以“精确地”表示为浮点数,因此,在约40亿个值的受祝福集合中没有的任何数字都无法表示。您可以从这个40亿个可表示的值池中简单地得到一个值,而不是仅仅出错,它是最接近您想要的值。

此外,由于计算机以二进制而不是十进制计数,所以您对“可表示”和“不可以表示”的感觉已经关闭。您可能会认为1/3是个大问题,但肯定1/10很容易,对吧?就是0.1,这是一个精确的表示。啊,但是十分之一在十进制中效果很好。毕竟,十进制是基于数字10的,这并不奇怪。但是用二进制?一半,四分之一,八分之一,十六分之一:二进制容易。十分之一?这是第三个困难:不可复制

0.9本身不是可表示的数字。但是,当您打印浮标时,就是这样。

原因是,打印浮动/双精度是一门艺术,而不是一门科学。假设只有少数数字是可表示的,并且由于二进制v。十进制的原因,这些数字对人类而言并不“自然”,那么您确实需要在数字上添加“取整”策略,否则看起来疯狂(没人想读0.899999999999999999765)。而这正是System.out.println和合作伙伴所做的。

但是您确实应该控制舍入功能:切勿使用System.out.println打印双精度和浮点型。请改用System.out.printf("%.6f",yourDouble);,在这种情况下,两者都将打印0.9。因为虽然两者都不能真正精确地表示0.9,但最接近浮点数的数字(或者更确切地说,就是取最接近2.0的数字(即2.0)和最接近1.1的数字(不是精确地1.1)),减去它们,然后找到最接近该结果的数字)–即使不是浮点数也打印为0.9,并且不打印为0.9的两倍。

,

floatdouble之间的区别:

让我们在一个简单的C程序中运行数字,以便获得其二进制表示形式:

#include <stdio.h>

typedef union {
    float val;
    struct {
        unsigned int fraction : 23;
        unsigned int exponent :  8;
        unsigned int sign     :  1;
    } bits;
} F;

typedef union {
    double val;
    struct {
        unsigned long long fraction : 52;
        unsigned long long exponent : 11;
        unsigned long long sign     :  1;
    } bits;
} D;

int main() {
    F f = {(float )(2.0 - 1.1)};
    D d = {(double)(2.0 - 1.1)};
    printf("%d %d %d\n",f.bits.sign,f.bits.exponent,f.bits.fraction);
    printf("%lld %lld %lld\n",d.bits.sign,d.bits.exponent,d.bits.fraction);
    return 0;
}

此代码的打印输出为:

0 126 6710886
0 1022 3602879701896396

基于上述两种格式规范,让我们将这些数字转换为有理值。

为了获得高精度,让我们在一个简单的Python程序中做到这一点:

from decimal import Decimal
from decimal import getcontext

getcontext().prec = 100

TWO = Decimal(2)

def convert(sign,exponent,fraction,e_len,f_len):
    return (-1) ** sign * TWO ** (exponent - 2 ** (e_len - 1) + 1) * (1 + fraction / TWO ** f_len)

def toFloat(sign,fraction):
    return convert(sign,8,23)

def toDouble(sign,11,52)

f = toFloat(0,126,6710886)
d = toDouble(0,1022,3602879701896396)

print('{:.40f}'.format(f))
print('{:.40f}'.format(d))

此代码的打印输出为:

0.8999999761581420898437500000000000000000
0.8999999999999999111821580299874767661094

如果我们在指定8到15个十进制数字的同时打印这两个值,那么我们将经历与您所观察到的相同的事情(double值打印为0.9,而float值打印接近0.9):

换句话说,这段代码:

for n in range(8,15 + 1):
    string = '{:.' + str(n) + 'f}';
    print(string.format(f))
    print(string.format(d))

给出此打印输出:

0.89999998
0.90000000
0.899999976
0.900000000
0.8999999762
0.9000000000
0.89999997616
0.90000000000
0.899999976158
0.900000000000
0.8999999761581
0.9000000000000
0.89999997615814
0.90000000000000
0.899999976158142
0.900000000000000

因此,我们的结论是Java默认情况下会以8到15位之间的精度打印小数。

好问题BTW ...

本文链接:https://www.f2er.com/3168664.html

大家都在问