如何获取浮点数的小数部分?

53
我需要提取浮点数的小数部分,但是我得到了奇怪的结果:
float n = 22.65f;
// I want x = 0.65f, but...

x = n % 1; // x = 0.6499996

x = n - Math.floor(n); // x = 0.6499996185302734

x = n - (int)n; // x = 0.6499996
为什么会这样?为什么我得到的值是那些值而不是 0.65

1
对于其他被问题描述所迷惑的人,整个讨论串都是在向用户解释,仅仅因为你将22.65作为文字量输入,并不意味着这个数字可以以二进制形式存储。浮点数并没有无限精度,如果你只是打印了n,你仍然会得到6.4999... - TrisT
12个回答

47

float类型只有少数几位有效数字,因此您很容易看到舍入误差。尝试使用double类型,它具有更高的精度,但仍然存在舍入误差。您必须对得到的任何答案进行四舍五入,以获得合理的输出。

如果这不是您想要的,您可以使用BigDecimal,它不会出现舍入误差,但在我看来会带来其他问题。

编辑:您可能会发现这很有趣。默认的Float.toString()方法使用最小的舍入,但通常不够。

System.out.println("With no rounding");
float n = 22.65f;
System.out.println("n= "+new BigDecimal(n));
float expected = 0.65f;
System.out.println("expected= "+new BigDecimal(expected));

System.out.println("n % 1= "+new BigDecimal(n % 1));
System.out.println("n - Math.floor(n) = "+new BigDecimal(n - Math.floor(n)));
System.out.println("n - (int)n= "+new BigDecimal(n - (int)n));

System.out.println("With rounding");
System.out.printf("n %% 1= %.2f%n", n % 1);
System.out.printf("n - Math.floor(n) = %.2f%n", n - Math.floor(n));
System.out.printf("n - (int)n= %.2f%n", n - (int)n);

打印

With no rounding
n= 22.6499996185302734375
expected= 0.64999997615814208984375
n % 1= 0.6499996185302734375
n - Math.floor(n) = 0.6499996185302734375
n - (int)n= 0.6499996185302734375
With rounding
n % 1= 0.65
n - Math.floor(n) = 0.65
n - (int)n= 0.65

+1 很棒的东西@Peter,谢谢。你有没有想过保证n - Math.floor(n)< 1.0?因为浮点数的奇怪性质,1.99999999999...是否会导致a >= 1? - Gray
1
@Gray 如果1.9999999999999999999实际上被表示为2,那么你才会得到奇怪的结果。如果从浮点数中减去整数部分,它不应该导致四舍五入。这可能会暴露底层的表示误差。通过删除整数部分,您为结果提供了比表示值所需更多的精度。你能举个例子吗? - Peter Lawrey
不,我不能@Peter。我只是在编写一些防御性代码。我很少使用浮点数(幸运的是),我只是在想。你的解释很有道理。谢谢。 - Gray

27

我认为这将是最简单的方法:

float n = 22.65f;
float x = n - (int) n;

4
这将产生舍入误差。 尝试: float a = 22.3f; a - (int)(a) 结果将为0.29999924而不是0.3。 - Prateek Batla

8

因为并非所有有理数都能表示为浮点数,而 0.6499996... 是最接近 0.65 的近似值。

例如,尝试打印数字 0.65 的前20位小数:

 System.out.printf("%.20f\n", 0.65f);

->

 0.64999997615814210000

编辑
正如其他人所指出的那样,计算过程中积累的舍入误差也是其中的一部分。


8

这是一段有点长但可行的代码:

BigDecimal.valueOf(2.65d).divideAndRemainder(BigDecimal.ONE)[1].floatValue()

有人能帮忙吗?这对我来说不是很明显。integerPart - BigDecimal.valueOf(weightInLB).divideAndRemainder(BigDecimal.ONE)[0].toInt() - Sergei S

6
获取浮点数和双精度数据类型的小数部分的正确方法是使用字符串,例如以下代码:
float num=2.35f;
String s= new Float(num).toString();
String p=s.substring(s.indexOf('.')+1,s.length());
int decimal=Integer.parseInt(p);

5
尝试这个。 如果计时器为10.65,那么h最终会成为前两个小数点* 100 = 65。 这是一种快速简便的方法,可以避免四舍五入问题。
float h = (int)((timer % 1) * 100);

1
@SvenRojek 不要对非代码文本使用代码格式。 - user207421
对于 50.01,由于使用的是浮点数据类型,有时会返回 0 - Sudhanshu Gaur

4

如果你只想将数字保留两位小数进行打印,可以使用DecimalFormat。

DecimalFormat df= new DecimalFormat("#.##");
System.out.println(df.format(f));

如果您希望在内部使用定点数,请使用BigDecimal。


3

添加了另一个有趣的 :) - Landei

0

那么(Kotlin方式)呢:

val a = 1.1234
val dec = a - a.toInt()
val decimals = dec * 10f.pow(dec.toString().length - 2)

将小数使用/转换为您想要的形式。


0
尝试使用{{link1:java.math.BigDecimal}}。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接