提问人:tunnuz 提问时间:3/10/2011 最后编辑:philipxytunnuz 更新时间:11/21/2023 访问量:55787
如何避免 Java 中浮点数或双精度数的浮点精度错误?[复制]
How to avoid floating point precision errors with floats or doubles in Java? [duplicate]
问:
我在 Java 中遇到了长数浮点数或双精度的问题。
如果我执行:
for ( float value = 0.0f; value < 1.0f; value += 0.1f )
System.out.println( value );
我得到:
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001
如何摆脱浮动精度误差的累积?
我尝试使用双打将误差减半,但结果是一样的。
答:
没有将 0.1 精确表示为 或 。由于此表示错误,结果与预期略有不同。float
double
您可以使用以下几种方法:
- 使用该类型时,请仅显示所需数量的数字。检查相等性时,无论哪种方式都允许较小的容差。
double
- 或者,使用允许您存储您尝试精确表示的数字的类型,例如可以精确表示 0.1。
BigDecimal
示例代码:BigDecimal
BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
value.compareTo(BigDecimal.ONE) < 0;
value = value.add(step)) {
System.out.println(value);
}
在线查看:ideone
您可以使用 BigDecimal
等类来避免此特定问题。 和 ,作为 IEEE 754 浮点,它们不是设计为完全准确,而是设计为快速。但请注意乔恩的观点:不能准确地表示“三分之一”,就像不能准确地表示“十分之一”一样。但是对于(比如)财务计算,像这样的类往往是要走的路,因为它们可以用我们人类倾向于思考它们的方式表示数字。float
double
BigDecimal
double
BigDecimal
评论
您应该使用十进制数据类型,而不是浮点数:
https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html
评论
这不仅仅是一个累积的错误(与Java完全无关)。,一旦转换为实际代码,就没有值 0.1 - 您已经收到舍入错误。1.0f
我能做些什么来避免这个问题?
这取决于什么样的 你正在做的计算。
- 如果你真的需要你的结果精确相加,特别是当你处理金钱时:使用一个特殊的十进制数据类型。
- 如果您只是不想看到所有这些额外的小数位:只需将结果格式四舍五入为固定 小数位数 显示它。
- 如果没有可用的十进制数据类型,另一种方法是工作 使用整数,例如do money 完全以美分计算。但 这是更多的工作,并且有一些 缺点。
有关详细信息,请阅读链接的网站。
另一种解决方案是放弃并检查这两个值是否足够接近。(我知道这不是你在正文中问的,但我正在回答问题标题。==
如果您想继续使用并通过重复添加来避免累积错误,请尝试如下操作:float
0.1f
for (int count = 0; count < 10; count++) {
float value = 0.1f * count;
System.out.println(value);
}
但是请注意,正如其他人已经解释的那样,这不是一个无限精确的数据类型。float
不要在迭代器中使用 float/double,因为这会使舍入误差最大化。如果您只使用以下
for (int i = 0; i < 10; i++)
System.out.println(i / 10.0);
它打印
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
我知道 BigDecimal 是一个受欢迎的选择,但我更喜欢 double,不是因为它更快,而是它通常更短/更清晰。
如果将符号数计为代码复杂度的度量
- 使用 double => 11 个符号
- 使用 BigDecimal(来自@Mark Byers 示例)=> 21 个符号
顺便说一句:除非有充分的理由不使用 double,否则不要使用 float。
您只需要了解计算所需的精度以及所选数据类型能够达到的精度,并相应地提供答案。
例如,如果要处理具有 3 个有效数字的数字,则使用 (提供 7 个有效数字的精度)是合适的。但是,如果起始值的精度仅为 2 个有效数字,则不能将最终答案引用为 7 个有效数字的精度。float
5.01 + 4.02 = 9.03 (to 3 significant figures)
在您的示例中,您正在执行多次加法,每次加法都会对最终精度产生影响。
为了完整起见,我推荐这个:
Shewchuck,“鲁棒自适应浮点几何谓词”,如果您想要更多有关如何使用浮点执行精确算术的示例 - 或者至少是作者的初衷的受控精度,http://www.cs.berkeley.edu/~jrs/papers/robustr.pdf
我遇到了同样的问题,使用 BigDecimal 解决了同样的问题。以下是帮助我的片段。
double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
total += (double)array[i];
bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);
希望对您有所帮助。
package loopinamdar;
import java.text.DecimalFormat;
public class loopinam {
static DecimalFormat valueFormat = new DecimalFormat("0.0");
public static void main(String[] args) {
for (float value = 0.0f; value < 1.0f; value += 0.1f)
System.out.println("" + valueFormat.format(value));
}
}
首先让它成为双倍。永远不要使用 float,否则您将在使用实用程序时遇到问题。java.lang.Math
现在,如果您碰巧事先知道您想要的精度,并且它等于或小于 15,那么很容易告诉您的双精度表现。检查以下内容:
// the magic method:
public final static double makePrecise(double value, int precision) {
double pow = Math.pow(10, precision);
long powValue = Math.round(pow * value);
return powValue / pow;
}
现在,每当您进行操作时,您必须告诉双精度结果的行为:
for ( double value = 0.0d; value < 1.0d; value += 0.1d )
System.out.println( makePrecise(value, 1) + " => " + value );
输出:
0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999
如果您需要超过 15 的精度,那么您就不走运了:
for ( double value = 0.0d; value < 1.0d; value += 0.1d )
System.out.println( makePrecise(value, 16) + " => " + value );
输出:
0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999
注1:为了提高性能,应将操作缓存在数组中。为了清楚起见,这里没有做。Math.pow
注2:这就是为什么我们从不使用双s 来表示价格,而是使用长s,其中最后 N(即 N <= 15,通常为 8)数字是十进制数字。那你就可以忘记我上面写的内容了:)
评论