- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章JAVA浮点数计算精度损失底层原理与解决方案由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
问题:
对两个double类型的值进行运算,有时会出现结果值异常的问题。比如:
1
2
3
4
|
System.out.println(
19.99
+
20
);
System.out.println(
1.0
-
0.66
);
System.out.println(
0.033
*
100
);
System.out.println(
12.3
/
100
);
|
输出:
1
2
3
4
|
39.989999999999995
0.33999999999999997
3.3000000000000003
0.12300000000000001
|
Java中的简单浮点数类型float和double不能够精确运算。这个问题其实不是JAVA的bug,因为计算机本身是二进制的,而浮点数实际上只是个近似值,所以从二进制转化为十进制浮点数时,精度容易丢失,导致精度下降.
关于精度损失的原理可以很简单的讲,首先一个正整数在计算机中表示使用01010形式表示的,浮点数也不例外.
比如11,11除以2等于5余1 。
5除以2等于2余1 。
2除以2等于1余0 。
1除以2等于0余1 。
所以11二进制表示为:1011. 。
double类型占8个字节,64位,第1位为符号位,后面11位是指数部分,剩余部分是有效数字.
正整数除以2肯定会有个尽头的,之后二进制还原成十进制只需要乘以2即可.
举个例子:0.99用的有效数字部分, 。
0.99 * 2 = 1+0.98 --> 1 0.98 * 2 = 1+0.96 --> 1 0.96 * 2 = 1+0.92 -- >1 0.92 * 2 = 1+0.84 -- >1 ............... 。
这样周而复始是没法有尽头的,而double有效数字有限,所以必定会有损失,所以二进制无法准确表示0.99,就像十进制无法准确表示1/3一样.
解决办法:
在《Effective Java》中提到一个原则,那就是float和double只能用来作科学计算或者是工程计算,但在商业计算中我们要用java.math.BigDecimal,通过使用BigDecimal类可以解决上述问题,首先需要注意的是,直接使用字符串来构造BigDecimal是绝对没有精度损失的,如果用double或者把double转化成string来构造BigDecimal依然会有精度损失,所以我觉得这种解决方法就是在使用中就把浮点数用string来表示存放,涉及到运算直接用string构造double,否则肯定会有精度损失.
1. 相加 。
1
2
3
4
5
6
7
8
9
10
11
|
/**
* 相加
* @param double1
* @param double2
* @return
*/
public
static
double
add(String doubleValA, String doubleValB) {
BigDecimal a2 =
new
BigDecimal(doubleValA);
BigDecimal b2 =
new
BigDecimal(doubleValB);
return
a2.add(b2).doubleValue();
}
|
2. 相减 。
1
2
3
4
5
6
7
8
9
10
11
|
/**
* 相减
* @param double1
* @param double2
* @return
*/
public
static
double
sub(String doubleValA, String doubleValB) {
BigDecimal a2 =
new
BigDecimal(doubleValA);
BigDecimal b2 =
new
BigDecimal(doubleValB);
return
a2.subtract(b2).doubleValue();
}
|
3. 相乘 。
1
2
3
4
5
6
7
8
9
10
11
|
/**
* 相乘
* @param double1
* @param double2
* @return
*/
public
static
double
mul(String doubleValA, String doubleValB) {
BigDecimal a2 =
new
BigDecimal(doubleValA);
BigDecimal b2 =
new
BigDecimal(doubleValB);
return
a2.multiply(b2).doubleValue();
}
|
4. 相除 。
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 相除
* @param double1
* @param double2
* @param scale 除不尽时指定精度
* @return
*/
public
static
double
div(String doubleValA, String doubleValB,
int
scale) {
BigDecimal a2 =
new
BigDecimal(doubleValA);
BigDecimal b2 =
new
BigDecimal(doubleValB);
return
a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
|
5. 主函数调用 。
1
2
3
4
5
6
7
8
|
public
static
void
main(String[] args) {
String doubleValA =
"3.14159267"
;
String doubleValB =
"2.358"
;
System.out.println(
"add:"
+ add(doubleValA, doubleValB));
System.out.println(
"sub:"
+ sub(doubleValA, doubleValB));
System.out.println(
"mul:"
+ mul(doubleValA, doubleValB));
System.out.println(
"div:"
+ div(doubleValA, doubleValB,
8
));
}
|
结果展示如下所示:
1
2
3
4
|
add:5.49959267
sub:0.78359267
mul:7.40787551586
div:1.33231241
|
所以最好的方法是完全抛弃double,用string和java.math.BigDecimal.
java遵照IEEE制定的浮点数表示法来进行float,double运算。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。具体底层如何存储以及如何进行运行请继续关注我的博客,后续我会将详情总结好的.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我! 。
原文链接:http://www.cnblogs.com/liang1101/p/6392179.html 。
最后此篇关于JAVA浮点数计算精度损失底层原理与解决方案的文章就讲到这里了,如果你想了解更多关于JAVA浮点数计算精度损失底层原理与解决方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个问题需要分而治之解决。有一个包含 N 个点的集合 S。如果有一个平行于轴的正方形,只包含S中的两个点p1和p2,则我们称p1和p2为 friend 点。 现在,我需要使用分而治之算法来计算 S
为 iPad 编程时,字体(和其他)大小以“磅”为单位指定。我已经看到将点作为独立于屏幕分辨率的像素的引用。但是我无法确定一个点的实际大小(即以英寸为单位)。一个点是否等于标准 iPad 屏幕上的一个
我有一个来自 Hadley Wickham 的 ggplot2 书中的问题。 我在这里有这个数据框: class % group_by(class) %>% summarise(n = n
好的,这是一些代码( pdfDocument 是 com.itextpdf.text.Document ): PdfPTable table = new PdfPTable(1); PdfPCell
我正在尝试添加一个 if 语句,如果小于 17,则将另一张牌添加到 DealerHand 中。 目前,它只是记录: 7 19 [ { suit: '♦', value: 9, points: 9 },
我正在编写一个程序,我需要: 对图像的每个像素进行测试 如果测试结果为真,我必须向点云中添加一个点 如果测试结果为假,什么都不做 我已经在 CPU 端 C++ 上编写了一个工作代码。现在我需要使用 C
我是一名优秀的程序员,十分优秀!