Java中将float转换为int的异常

Asa*_*waz 0 java floating-point

正如标题所述,我正在尝试将一些浮点数转换为整数,并且我看到了一些异常值。例如,我有一种方法试图将浮点数的小数部分转换为 .45 from 123.45字符串表示形式,并在其中输出为45/100.

问题是0.35, 0.45, 0.65 and 0.950.34, 0.44, 0.64 and 0.94 分别得到。这是我的实现:

public String convertDecimalPartToString(float input){

    int numberOfDigits = numberOfDigits(input);

    System.out.println(numberOfDigits);

    String numerator = Integer.toString((int) (input * Math.pow(10, numberOfDigits)));

    String denominator = Integer.toString((int) Math.pow(10, numberOfDigits));

    return numerator + "/" + denominator;
}

public int numberOfDigits(float input){

    String inputAsString = Float.toString(input);

    System.out.println(inputAsString);

    int digits = 0;
    // go to less than 2 because first 2 will be 0 and .
    for(int i =0;i<inputAsString.length()-2;i++){
        digits++;
    }
    return digits;
}
Run Code Online (Sandbox Code Playgroud)

我的测试方法如下所示:

@Test
public void testConvertDecimalPartToString(){
    float input = 0.95f;
    //output is 94/100 and assert fails
    Assert.assertEquals("95/100", checkWriter.convertDecimalPartToString(input));

}
Run Code Online (Sandbox Code Playgroud)

这行似乎计算错误: String numerator = Integer.toString((int) (input * Math.pow(10, numberOfDigits))); 但我不明白为什么它适用于0.05, 0.15, 0.25, 0.55, 0.75 and 0.85.

有人可以帮我理解吗?

rzw*_*oot 6

问题是有福的数字。想象一下,我给了你 3 位(0 或 1 个值):你只能用这个表示 8 个不同的值:000、001、010、011、100、101、110 和 111。这就是它们的全部。如果这是唯一的合法值,则不能表示超过 8 个概念!

float是一个 32 位值。使用 32 位,我可以为您提供多达 40 亿个不同的值。这是很多值,但它不是无限的,但是在 0 和 1 之间有无限的数字,更不用说在 0 和340,282,346,638,528,860,000,000,000,000,000,000,000.000000afloat可以表示的最大值之间。

那么它是如何工作的呢?好吧,并不是每个数字实际上都可以表示。只有大约 40 亿个数字。这些是有福的数字。

任何时候您尝试生成一个非祝福数,或者计算结果未得到祝福时,您的数字都会四舍五入到最接近的祝福数,如果您执行一系列操作,则每一步都会进行舍入。

祝福数的性质是这样的,0 和 1 之间的祝福数与 1 以上的数一样多 - 当您远离 0 时,任何 2 个祝福数之间的间隔都会增加。事实上,最终它会超过 1.0。

此外,计算机以二进制计数,而不是十进制计数。就像你不能用十进制表示“1 除以 3”一样(0.33333……它永远不会结束),像 0.1(1 除以 10 件事)这样简单的东西也不能用二进制完美地表示,所以像1.0/10.0已经四舍五入这样简单的东西!

如果 _any_rounding 发生,您的代码将失败。您的情况的解决方案相当简单;添加 0.005 就可以了。更好的方法是首先将其渲染为圆角字符串:

String x = String.format("%.02f", yourValue);
Run Code Online (Sandbox Code Playgroud)

然后在字符串中找到你需要的东西。以上处理适当的舍入,并且比使用 Math.pow 做得更好,当它使您远离 0 时,会导致出现更多错误(远离 0 -> 更极端的舍入错误,因为有更少的祝福数字那么远)。

注意:请注意,double它与 float 一样快,并且考虑到您有 64 位可以在那里花费,因此有更多的祝福数字,因此错误更少。

NB2:另一种方法是移动您的“单位”概念。例如,如果这代表现金,int cents;那就有- 没有问题,更容易知道祝福数字是什么int(-2 ^ 31和+ 2 ^ 31之间的每个整数)。