Java中的小数运算与精度损失
Java中的小数运算与精度损失
float、double类型的问题
我们都知道,计算机是使用二进制存储数据的。而平常生活中,大多数情况下我们都是使用的十进制,因此计算机显示给我们看的内容大多数也是十进制的,这就使得很多时候数据需要在二进制与十进制之间进行转换。对于整数来说,两种进制可以做到一一对应。而对于小数来讲就不是这样的啦。
我们先来看看十进制小数转二进制小数的方法
对小数点以后的数乘以2,会得到一个结果,取结果的整数部分(不是1就是0),然后再用小数部分再乘以2,再取结果的整数部分……以此类推,直到小数部分为0或者位数已经够了。顺序取每次运算得到的整数部分,即为转换后的小数部分。
演示:
0.125 ×2=0.25 .......................0
0.25×2=0.5.............................0
0.5×2=1.0................................1
即 0.125的二进制表示为小数部分为0.001
其实我们可以看出,这种方法实质上就是用1/2,1/4,8/1...来组合加出我们要转换的数据值,但显然不是所有的数都能够组合出来的。如0.1。
0.1×2=0.2 .....................0
0.2×2=0.4 ......................0
0.4×2=0.8 .....................0
0.8×2=1.6.......................1
0.6×2=1.2.......................1
0.2×2=0.4.......................0
.....
从上述计算过程我们可以看出,这是个无限小数,所以在这种情况下我们的float、double只能舍去一些位。
那为什么我们在直接给float赋值在输出时没有看到精度损失而在运算时却会出现呢?
确实是这样,如下
float a = 0.2f;
System.out.println(a);
//输出0.2
对于上述情况我只是查了资料,好像是因为编译器会进行优化,当我们存储的数据特别接近的时候,编译器会很贴心的返回我们想看到的数值(即二进制浮点数并不能准确的表示0.1这个十进制小数,它使用了0.100000001490116119384765625来代替0.1。),至于到了运算中,就会出现精度损失较大从而看到了真相。如果这块说的不对欢迎小伙伴们在评论区指正!
解决方法
BigDecimal 原理
我们一般会使用
BigDecimal 来避免出现精度丢失问题,至于为什么BigDecimal 可以避免,而float或double不行,我们在此不详细讨论,简单来说就是BigDecimal 通过借助整数来表示小数的方式,因为对于整数而言,二进制和十进制是完全一一对应的,用整数来表示小数,再记录下小数的位数,就可以完美的解决该问题。
BigDecimal 用法
java.math.BinInteger 类和 java.math.BigDecimal 类都是Java提供的用于高精度计算的类.其中 BigInteger 类是针对大整数的处理类,而 BigDecimal 类则是针对大小数的处理类.
BigDecimal构造方法
BigDecimal BigDecimal(double d); //不允许使用
BigDecimal BigDecimal(String s); //常用,推荐使用
static BigDecimal valueOf(double d); //常用,推荐使用
double 参数的构造方法,不允许使用!!!!因为它不能精确的得到相应的值;
String 构造方法是完全可预知的: 写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的0.1; 因此,通常建议优先使用 String 构造方法;
静态方法 valueOf(double val) 内部实现,仍是将 double 类型转为 String 类型; 这通常是将 double(或float)转化为 BigDecimal 的首选方法;
测试
System.out.println(new BigDecimal(0.1));
System.out.println(BigDecimal.valueOf(0.1));
\输出*
0.1000000000000000055511151231257827021181583404541015625
0.1
BigDecimal常用操作
我们通过一个工具类源码来体会BigDecimal的常规用法
package com.util;
import java.math.BigDecimal;
/**
- 提供精确的浮点数运算(包括加、减、乘、除、四舍五入)工具类
*/
public class ArithUtil {
// 除法运算默认精度 private static final int DEF_DIV_SCALE = 10; private ArithUtil() { } /** * 精确加法 */ public static double add(double value1, double value2) { BigDecimal b1 = BigDecimal.valueOf(value1); BigDecimal b2 = BigDecimal.valueOf(value2); return b1.add(b2).doubleValue(); } /** * 精确减法 */ public static double sub(double value1, double value2) { BigDecimal b1 = BigDecimal.valueOf(value1); BigDecimal b2 = BigDecimal.valueOf(value2); return b1.subtract(b2).doubleValue(); } /** * 精确乘法 */ public static double mul(double value1, double value2) { BigDecimal b1 = BigDecimal.valueOf(value1); BigDecimal b2 = BigDecimal.valueOf(value2); return b1.multiply(b2).doubleValue(); } /** * 精确除法 使用默认精度 */ public static double div(double value1, double value2) throws IllegalAccessException { return div(value1, value2, DEF_DIV_SCALE); } /** * 精确除法 * @param scale 精度 */ public static double div(double value1, double value2, int scale) throws IllegalAccessException { if(scale < 0) { throw new IllegalAccessException("精确度不能小于0"); } BigDecimal b1 = BigDecimal.valueOf(value1); BigDecimal b2 = BigDecimal.valueOf(value2); // return b1.divide(b2, scale).doubleValue(); return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 四舍五入 * @param scale 小数点后保留几位 */ public static double round(double v, int scale) throws IllegalAccessException { return div(v, 1, scale); } /** * 比较大小 */ public static boolean equalTo(BigDecimal b1, BigDecimal b2) { if(b1 == null || b2 == null) { return false; } return 0 == b1.compareTo(b2); }
}
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
透彻理解C++11新特性:右值引用、std::move、std::forward
透彻理解C++11新特性:右值引用、std::move、std::forward 目录浅拷贝、深拷贝左值、右值右值引用类型强转右值 std::move重新审视右值引用右值引用类型和右值的关系函数参数传递函数返还值传递万能引用引用折叠完美转发 std::forwardC++11出现的右值相关语法可谓是很多C++程序员难以理解的新特性,不少人知其然而不知其所以然,面试被问到时大概就只知道可以减少开销,但是为什么减少开销、减少了多少开销、什么时候用...这些问题也不一定知道,于是我写下了这篇夹带自己理解的博文,希望它对你有所帮助。 浅拷贝、深拷贝在介绍右值引用等概念之前,可以先来认识下浅拷贝(shallow copy)和深拷贝(deep copy)。 这里举个例子: class Vector{ int num; int* a; public: void ShallowCopy(Vector& v); void DeepCopy(Vector& v); };浅拷贝:按位拷贝对象,创建的新对象有着原始对象属性值的一份精确拷贝(但不包括指针指向的内存)。//浅拷贝void Vect...
- 下一篇
C#多线程(16):手把手教你撸一个工作流
C#多线程(16):手把手教你撸一个工作流 目录前言节点ThenParallelScheduleDelay试用一下顺序节点并行任务编写工作流接口构建器工作流构建器依赖注入实现工作流解析前言前面学习了很多多线程和任务的基础知识,这里要来实践一下啦。通过本篇教程,你可以写出一个简单的工作流引擎。 本篇教程内容完成是基于任务的,只需要看过笔者的三篇关于异步的文章,掌握 C# 基础,即可轻松完成。 C#多线程(13):任务基础①C#多线程(14):任务基础②C#多线程(15):任务基础③由于本篇文章编写的工作流程序,主要使用任务,有些逻辑过程会比较难理解,多测试一下就好。代码主要还是 C# 基础,为什么说简单? 不包含 async 、await几乎不含包含多线程(有个读写锁)不包含表达式树几乎不含反射(有个小地方需要反射一下,但是非常简单)没有复杂的算法因为是基于任务(Task)的,所以可以轻松设计组合流程,组成复杂的工作流。 由于只是讲述基础,所以不会包含很多种流程控制,这里只实现一些简单的。 先说明,别用到业务上。。。这个工作流非常简单,就几个功能,这个工作流是基于笔者的多线程系列文章的知...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Mario游戏-低调大师作品
- 2048小游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案