Java中double精度丢失如何处理

2023-10-27

图片来自网络 


      代码审查(CodeReview)是一种可以有效提高代码质量的方法。他可以帮助团体提高产品代码质量,提高产品的稳定性。更容易维护的代码会带来更少的技术债务,从整体上看,提高了软件开发和迭代的效率。


 double精度丢失现象


        程序开发时,Java开发人员常常使用double,但很多人都没有关注到double精度问题, 代码审查中遇到了double精度丢失,因此有了这边笔记

         项目中,对方接口规范中明确费用使用String字符串类型,单位是分, 例如10元表示为字符串1000,代码走查时发现开发人员在业务系统中的DTO对象用Double来存储费用值;调用接口前将Double转换为String,仔细看发现转换处理存在问题,原始代码如下

//先乘以100转换成分
double fee = money*100;
//然后将分装换成字符串
itemDTO.setITEM_FEE(String.valueOf((int)fee));

运行下面的测试代码,会发现费用是2.51元,期望转换为字符串为251,但是实际上转换后的结果是250

public static void main(String[] args) {
	Double money = 2.51d;
	double fee = money * 100;
	System.out.println(String.valueOf((int) fee));
}
//输出结果:250

这是由于double不是精确计算,存在精度丢失。运行下面的测试代码,会发现2.51d乘以100输出结果为250.99999999999997并不是251,再将250.99999999999997强转为整型int,那么小数点后的数值全部丢失 ,参考测试代码

public static void main(String[] args) {
	Double money = 2.51d;
	double fee = money * 100;
	System.out.println(fee);
}
//输出结果:250.99999999999997


为什么会精度丢失


         不论是double还是float都是浮点数, 计算机进行计算的时候采用二进制来计算,先将10进制转换成二进制,然后进行计算,最后再将二进制转换为十进制。浮点数会失去一定的精度

         十进制转换二进制处理分整数部分和小数部分处理不同

1. 整数部分处理方法

除2取余法,即每次将整数部分除以2,余数为该位权上的数,而商继续除以2,余数又为上一个位权上的数,这个步骤一直持续下去,直到商为0为止,最后读数时候,从最后一个余数读起,一直到最前面的一个余数

例如25转换为二进制的步骤为

  • 第一步将25除以2 商12余1
  • 第二步将12除以2 商6余0
  • 第三步将6除以2 ,商3余0
  • 第四步将3除以2, 商1余1
  • 第五步将1除以2,商0余1
  • 第六步读数:(从下往上) 11001

2. 小数部分处理方法

  乘2取整法,即将小数部分乘以2,然后取整数部分,剩下的小数部分继续乘以2,然后取整数部分,剩下的小数部分又乘以2,一直取到小数部分 为零为止。如果永远不能为零,就同十进制数的四舍五入一样,按照要求保留多少位小数时,就根据后面一位是0还是1,取舍,如果是零,舍掉,如果是1,向入一位。换句话说就是0舍1入。读数要从前面的整数读到后面的整数

例如将0.125转换为二进制数

  • 第一步将0.125乘以2,得0.25,则整数部分为0,小数部分为0.25

  • 第二步将小数部分0.25乘以2,得0.5,则整数部分为0,小数部分为0.5

  • 第三步将小数部分0.5乘以2,得1.0,则整数部分为1,小数部分为0.0

  • 第四步读数,从上往下,即为0.001 

当然有的数值小数部分永远无法是0,那么就会做取舍操作


如何处理double精度丢失问题


有多重方法可以处理上述问题,但需要根据具体业务来定


1. Math函数使用


简单处理,在简单也业务背景下可以使用Math函数实现四舍五入处理

public static void main(String[] args) {

	Double fee2 = Double.parseDouble("2.51");
	System.out.println(Math.round(fee2 * 100));
}
//输出结果:251

常用Math函数方法

  • Math.abs() 绝对值 ,例如 Math.abs(-3); 输出3,是整型就返回整型,浮点型就返回浮点型
  • Math.round() 四舍五入,例如 Math.round(4.798); 输出5,返回的是整型
  • Math.rint() 也是四舍五入,返回的是double型。但和Math.round() 不同的是,当Math.rint(2.5); 输出的是 2.0,rint判断四舍五入时,当如果距离两边的整数距离相同则取偶数,负数也同理。
  • Math.random() 获取随机数,随机数的范围是 0.0 =< Math.random < 1.0,返回的是double型
  • Math.pow() 计算次方,例如 Math.pow(4,2) ; 输出16.0 ,返回的是double型
  • Math.ceil() 向上取整,ceil是天花板的意思,例如 Math.ceil(106.789); 输出107.0,返回的是double型
  • Math.floor() 向下取整,floor是地板的意思,例如 Math.floor(93.881); 输出93.0,返回的是double型
  • Math.E 是自然对数,e=2.7182818 Math.PI 是圆周率, Π=3.1415926
  • Math.exp(x) 计算e^x, e是自然对数e=2.7182818 ,例如 Math.exp(1); 输出2.7182818
  • Math.sqrt() 求算数平方根,例如 Math.sqrt(16); 输出4.0,返回的是double型
  • Math.log() 计算以自然数为底数的对数值,自然数e=2.7182818,例如 Math.log(11.635);输出2.454,返回的是double型
  • Math.log10() 计算以10为底数的对数值,例如 Math.log10(100); 输出2.0,返回的是double型
  • Math.min() 返回两个数中的最小值 Math.max() 返回两个数中的最大值
  • Math.toDegrees() 将弧度转换成角度,例如 Math.toDegrees(Math.PI/4); 输出 45.0,返回的是double型
  • Math.toRadians() 将角度转换为弧度,例如 Math.toRadians(45); 输出 Π/4
  • Math.sin() 计算正弦值,例如 Math.sin(Math.PI/2); 输出1.0;返回的是double型,
  • Math.cos() 计算余弦值,例如 Math.cos(Math.PI); 输出-1.0;返回的是double型,
  • Math.tan() 计算正切值,例如 Math.tan(Math.PI/4); 输出1.0;返回的是double型,
  • ⭐需要注意的是括号里面的参数是写弧度值,而不是写角度制
  • Math.asin() 通过正弦值计算角度,输出的是弧度制,例如 Math.asin(0.5); 输出 Π/6
  • Math.acos() 通过余弦值计算角度,输出的是弧度制,例如 Math.acos(0.5); 输出 Π/3
  • Math.atan() 通过正切值计算角度,输出的是弧度制,例如 Math.atan(1); 输出 Π/4


2.BigDecimal使用


        Java在java.math包中提供的API类 java.math.BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算

1.BigDecimal运算

       在商业计算中因为精度问题可能会造成业务数据不准确,所以一般要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象

  • add(BigDecimal)          BigDecimal对象中的值相加,然后返回这个对象

  • subtract(BigDecimal)   BigDecimal对象中的值相减,然后返回这个对象

  • multiply(BigDecimal)    BigDecimal对象中的值相乘,然后返回这个对象

  • divide(BigDecimal)       BigDecimal对象中的值相除,然后返回这个对象


2. BigDecimal构造器注意事项

  • BigDecimal(int)          创建一个具有参数所指定整数值的对象。

  • BigDecimal(double)   创建一个具有参数所指定双精度值的对象。

  • BigDecimal(long)       创建一个具有参数所指定长整数值的对象。

  • BigDecimal(String)    创建一个具有参数所指定以字符串表示的数值的对象

参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1,但是它实际上等于0.1000000000000000055511151231。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言, 通常建议优先使用String构造方法。 

当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。也可以使用static valueOf(double)方法。

参考下面的测试代码:

public static void main(String[] args) {
	BigDecimal b1=new BigDecimal(2.51d);
	System.out.println(b1);
	System.out.println(b1.multiply(new BigDecimal(100)));
	System.out.println(b1.multiply(new BigDecimal(100)).toBigInteger());
		
	System.out.println();
	BigDecimal b2=new BigDecimal(Double.toString(2.51d));
	System.out.println(b2);
	System.out.println(b2.multiply(new BigDecimal("100")));
	System.out.println(b2.multiply(new BigDecimal("100")).toBigInteger());
}

测试结果
2.5099999999999997868371792719699442386627197265625
250.9999999999999786837179271969944238662719726562500
250

2.51
251.00
251


3.BigDecimal四舍五入

BigDecimal.setScale()方法用于格式化小数点

  • setScale(1)表示保留一位小数,默认用四舍五入方式 

  • setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3 

  • setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4 

  • setScaler(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35变成2.3,如果是5则向下取舍

  • setScaler(1,BigDecimal.ROUND_CEILING)接近正无穷大的舍入

  • setScaler(1,BigDecimal.ROUND_FLOOR)接近负无穷大的舍入,数字>0和ROUND_UP作用一样,数字<0和ROUND_DOWN作用一样

  • setScaler(1,BigDecimal.ROUND_HALF_EVEN)向最接近的数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入


上一篇:Java中serialVersionUID作用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中double精度丢失如何处理 的相关文章

  • 使用 ScriptEngine 从 JavaScript 调用 Java 方法

    我正在使用 ScriptEngine 运行 JavaScript 我希望 JavaScript 脚本能够调用 myFunction 其中 myFunction 是我的给定类中的一个方法 我知道可以将 importPackage 用于标准 J
  • Javadoc 链接到其他类中的方法

    目前我正在使用以下 Javadoc 语法引用其他类中的方法 see link com my package Class method 据我从文档中了解到 这是执行此操作的正确方法 但现在到了有趣的部分 或者说令人沮丧的部分 当我生成这个 j
  • android新手需要了解“?android:attr/actionBarSize”

    我正在经历拉尔斯 沃格尔的教程 http www vogella com articles AndroidFragments article html在使用 Fragments 时 我遇到了以下代码 android layout margi
  • java中的散列是如何工作的?

    我正在尝试弄清楚java中的哈希值 例如 如果我想在哈希图中存储一些数据 它是否会有某种带有哈希值的底层哈希表 或者 如果有人能够对哈希的工作原理给出一个很好且简单的解释 我将非常感激 HashMap 基本上在内部实现为数组Entry 如果
  • Netbeans 雷达插件配置

    我使用的是 Netbeans 8 0 1 在提交到 SVN 之前 我需要从 IDE 运行并检查 SonarQube 分析 我已经安装了 Netbeans Radar 插件 用于启动本地分析并检查结果 这个插件有一个名为 Get Issues
  • 当Java中set已经是原子的时候,为什么我们还需要compareAndSet呢?

    因为原子意味着线程安全 当 set 本身在java中是原子和线程安全的时候 我们什么时候使用compareAndSet 举例来说 我想以原子方式设置一个变量 以便每个其他线程都可以看到它 但我希望以线程安全的方式设置该变量 我可以简单地将其
  • Java TCP Echo 服务器 - 广播

    我有一个简单的回显服务器 我希望当连接的用户向服务器键入任何内容时 所有其他客户端和该客户端都会收到消息 MOD 它现在不会发送给所有客户端 但它应该发送 而且我只是不知道我的代码出了什么问题 所以现在它只会将消息 MOD 发送给发送消息的
  • 控制启动时的竞争条件

    我有一些代码想要执行一些一次性初始化 但这段代码没有明确的生命周期 因此在初始化完成之前 我的逻辑可能会被多个线程调用 所以 我想基本上确保我的逻辑代码 等待 直到初始化完成 这是我的第一次剪辑 public class MyClass p
  • 无法从 PDFA1-a 格式文档中提取图像

    我正在使用以下代码从 PDFA1 a 格式的 pdf 中提取图像 但我无法获取图像 List
  • 如何修复运行 Android 模拟器时出现 GPU Driver Issue 错误

    我的 Android 模拟器几周前运行良好 但现在出现错误 当我运行代码时 GPU 驱动程序问题错误对话框与模拟器一起弹出 当我单击 确定 时 Android 模拟器不会按预期运行应用程序 错误如下 Your GPU driver info
  • 异步不适用于控制器的抽象超类方法

    我有一个BaseRestControllerRest 控制器扩展的类 它有一个我想异步运行的方法 public abstract class BaseRestController Async someThreadPoolTaskExecut
  • Apache Camel - 路由中的事务

    我有一个关于 Apache Camel 的一般性问题 我无法找到聚合器是否已进行交易 如果是交易 交易是如何实现的 聚合的速度有多快 将消息发送到聚合器可以在事务中运行 您需要一个带有聚合器的持久存储来让传出消息充当事务 请参阅有关持久性的
  • 错误:类 kotlin.reflect.jvm.internal.FunctionCaller$FieldSetter

    我已尝试一切方法来消除此错误 但它不断出现 Class kotlin reflect jvm internal FunctionCaller FieldSetter can not access a member of class com
  • 在Linux中执行jar文件[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我创建了一个可执行的 Java jar 文件 也就是说 我将 java 程序正确打包到 jar 文件中 包括 META INF MANIFEST 文件
  • GSSAPI 中的 javax.naming.AuthenticationException

    我正在尝试使用 JAVA GSSAPI 执行 NTLM 绑定 我收到此错误 javax naming AuthenticationException GSSAPI 根异常是 javax security sasl SaslException
  • CXF 增加连接池大小而不更改 http.maxConnections

    最近我被要求将 CXF 配置为与我们旧的 XFire 服务相同的参数 这些参数之一是Keep Alive timeout 60 max 20 然而 我做了一些研究 看来 CXF 使用 JVMHttpURLConnection引擎盖下的对象
  • 使用会话空闲超时进行轮询

    我对 Tomcat 中的所有应用程序使用单点登录 我的要求是 我必须轮询应从后端获取的事务状态 但它也不应该影响会话的空闲超时 有人可以建议是否可以做点什么吗 Thanx 我不知道是否有标准方法可以做到这一点 如果没有 你可以写一个过滤器
  • 在服务器上创建 Zip 文件并使用 java 下载该 zip

    我从 mkyong 获得了以下代码 用于在本地压缩文件 但是 我的要求是在服务器上压缩文件并需要下载它 任何人都可以帮忙吗 代码写入zip文件 public void zipFiles File contentFile File navFi
  • Java:易失性足以使类线程安全?

    我有一个关于 Java 中 volatile 语句的问题 请看这个构造的例子 class Master Foo is a class with thread safe methods public volatile Foo foo clas
  • Java编程编译jar

    我有一个文本文件中的java源代码 必须在源代码中输入一些自定义的硬编码变量 然后将其转换为 jar 这是可行的 但是当我运行 jar 时 找不到 Main 类 当我用 WinRAR 解压 jar 文件时 我似乎找不到错误 当我通过 cmd

随机推荐

  • eclipse 项目提示"Project facet Java version 1.8 is not supported"

    今天使用eclipse 工具 导入maven 项目 编译项目的时候提示 Project facet Java version 1 8 is not supported 第一步 排查eclipse 项目的编译版本 项目 properties
  • Angular-官方文档学习-1

    Angular 简介 AngularJS 是一个 JavaScript 框架 它可通过 AngularJS 通过 指令 扩展了 HTML 且通过 表达式 绑定数据到 HTML AngularJS 扩展了 HTML AngularJS 通过
  • 选择文件窗口,获取选择文件地址

    微信公众号原文 系统 Windows 7 软件 Excel 2010 学习路径图 针对之前的学习路径图 会针对的写一些文章 我们在做信息处理的时候 可能会涉及到多个其它文件 有的时候需要根据需求选择所需文件进行处理 今天我们就讲讲如何使用V
  • 软件测试的基本概念

    目录 一 什么是需求 二 什么是测试用例 三 什么是BUG 四 开发模型和测试模型 1 软件开发生命周期 2 软件开发的五大模型 2 1 瀑布模型 2 2螺旋模型 2 3增量模型 迭代模型 2 4 敏捷模型 3 软件测试的两大模型 3 1
  • 一个登录案例学会 Pinia

    Pinia 号称下一代的 Vuex 经过初步体验 发现相比于 Vuex Pinia 确实有了很大进步 最明显的就是删减了复杂的概念 简化了数据流转的过程 现在只剩下了 store state getters actions 这四个核心概念
  • 小白学习python——numpy

    零 初识numpy 1 numPy Numerical Python 即数值Python包 是Python进行科学计算的一个基础包 所以是一个掌握其他Scipy库中模块的基础模块 一定需要先掌握该包的主要使用方式 官网 http www n
  • 猿创征文

    内存管理实现单链表的插入和删除 1 收获 2 什么是单链表 3 节点的创建 4 主函数的实现 5 子函数的实现 5 1 AollocNode的实现 5 2 HeadInsertNode的实现 5 3 ShowNode的实现 5 4 Head
  • KB2871997补丁绕过

    KB2871997补丁绕过 微软为了防止用户的明文密码在内存中泄露 发布了KB2871997补丁 关闭了Wdigest功能 Windows Server2012及以上版本默认关闭Wdigest 使攻击者无法从内存中获取明文密码 Window
  • 24. 两两交换链表中的节点

    给你一个链表 两两交换其中相邻的节点 并返回交换后链表的头节点 你必须在不修改节点内部的值的情况下完成本题 即 只能进行节点交换 输入 head 1 2 3 4 输出 2 1 4 3 示例 2 输入 head 输出 示例 3 输入 head
  • php 验证只能输入姓名,php 检查输入用户名是否符合规定示例

    这篇文章主要为大家详细介绍了php 检查输入用户名是否符合规定示例 具有一定的参考价值 可以用来参考一下 对php检查输入的用户名是否符合规定感兴趣的小伙伴 下面一起跟随512笔记的小编两巴掌来看看吧 php检查输入的用户名是否符合规定 p
  • 【热门框架】Mybatis-Plus条件查询的三种格式

    Mybatis Plus 提供了三种常用的条件查询方式 分别是 Wrapper QueryWrapper LambdaQueryWrapper Wrapper Wrapper 是一个接口 提供了若干个构造方法 可以用来构建 where 条件
  • C++可变参数模板

    可变参数模板 接受可变数目参数的模板函数或模板类 将可变数目的参数成为参数包 有模板参数包和函数参数包 模板参数包 表示零个或多个模板参数 函数参数包 表示零个或多个函数参数 例如 template
  • 数据挖掘note(1)

    数据挖掘一般分为机器学习和统计学习 大数据学的课程一般是关于机器学习 我们学的浅 主要关于统计学习 示意图如下所示 这是一个大数据时代 但是数据挖掘的利用率不足0 5 可见数据挖掘的空间巨大 问题 数据挖掘对信息安全有什么用 例如从几十万条
  • VTM2.0+360lib-7.0配置环境

    全景视频编码跟普通的视频编码不一样 在VTM平台下还需要配置一个360lib 这里贴一下VTM和360lib的地址 VTM下载地址 360lib下载地址 提示一下 VTM可以直接在网站上下载zip版本 360lib要svn的方法下载 下载下
  • 戏开发unity编译和调试系列:The type or namespace name ‘NativeList<>‘ could not be found

    The type or namespace name NativeList lt gt could not be found are you missing a using directive or an assembly referenc
  • intellij idea如何将基于Springboot的web项目打成war包

    intellij idea如何将基于Springboot的web项目打成war包 详细内容请参看 https ms200 cn p 791
  • flink接入Kafka报错:timeout expired while fetching topic metadata

    简单的flink接入kafka结果报错 代码 create env val env StreamExecutionEnvironment getExecutionEnvironment set parallelism env setPara
  • 测试员不可不知的几款bug管理工具

    根据每个公司性质的不同 规模的不同 所用到的bug管理工具也可能不同 你们用的bug管理工具是什么呢 下面介绍几款主流的bug管理工具 1 JIRA 付费 JIRA JIRA的生产者把JIRA定义为Professional Issue Tr
  • Git:Git中的分支管理

    文章目录 分支是什么 创建分支 分支切换 合并分支 删除分支 合并冲突 合并模式 分支策略 bug分支 强制删除分支 本篇主要总结的是Git中的分支管理 分支是什么 在Git中 一个强大的功能就是分支 由前面的学习可以知道 当我们每次进行c
  • Java中double精度丢失如何处理

    图片来自网络 代码审查 CodeReview 是一种可以有效提高代码质量的方法 他可以帮助团体提高产品代码质量 提高产品的稳定性 更容易维护的代码会带来更少的技术债务 从整体上看 提高了软件开发和迭代的效率 double精度丢失现象 程序开