支付价格计算中精度问题之double,float

前言

  • 前段时间开发新的微信小程序,借此机会将老掉牙的支付模块重构,并且支持现金支付(之前都是虚拟币支付),在重构期间遇到计算上的一些精度问题,虽然数额影响非常小但是影响比较大,我觉得有必要总结以下遇到的一些问题,并且解决弄清楚他的原来,因此有下文。

先看几个现象

  • 当我们程序中涉及到一些double或者float类型的数据,并且精度要求比较高,小数点位数比较多的时候,可能会出现一些非常奇葩的问题,我下面针对遇到的一些问题给出几个说明案例:
条件判断异常
//比较特殊的案例
System.out.println(1f == 0.9999999f);   //false
System.out.println(1f == 0.99999999f);   //true
数据转换异常
float f = 0.6f;
double d1 = 0.6d;
double d2 = f;
System.out.println((d1 == d2) + "  " + f + "    " + d2);//false  0.6 
基本运算异常
 System.out.println( 0.2 + 0.7 ); //0.8999999999999999
数据自增异常
   float f1 = 8455263f;for (int i = 0; i < 10; i++) {System.out.println(f1);f1++;}
//8455263.0
//8455264.0
//8455265.0
//8455266.0
//8455267.0
//8455268.0
//8455269.0
//8455270.0
//8455271.0
//8455272.0float f2 = 84552631f;for (int i = 0; i < 10; i++) {System.out.println(f2);f2++;}
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7
//8.4552632E7

Java中浮点类型精度问题

  • 要搞清除还是得从java的double和float类型来入手,我们都知道,计算机只认识二进制的0,1,那么在double和float中有整数部分,小数部分,如此说来那么应该会有一定的方式将小数转为计算机认识的0,1 组合,这中方法是定义的一个转换标准,其实而Java中浮点数采用的是IEEE 754标准
IEEE745 标准
  • IEEE 745 是IEEE二进制浮点数算数标准(Standard for Floating-Point Arthmetic)的标准编号,等同一个国际标准ISO之类,是美国哪家公司订的,这个标准定义来表示浮点数的格式(包括负零-0)与反常值(denormal number),一些特殊数值(例如无穷inf,非数值NaN)以及一些数值的“浮点数运算子”它知名来四种数值修约规则和五种例外状况。还有要了解的可以去JDK官网

浮点数的组成结构

  • 我们学习计算机组成原理的时候,应该学过的,Java中表示小数的时候有三个组成部分:
    • 符号位 S
    • 阶码部分 E
    • 尾数部分 M
  • 这三个纬度的信息,一个浮点数表示就可以完全确认下来,如下图所示的存储结构
    在这里插入图片描述
  • 符号位部分 S: 0 表示正数, 1 表示负数
  • 阶码部分E (只整数部分):
    • 对于float型浮点数,指数部分8 位,考虑可正负,因此可以表示的指数范围是-127~128
    • 对于double类型浮点数,指数部分11 为,可正负,因此可以表示的指数范围是-1203~1024
  • 尾数部分 M:
    • 对于float类型来说,尾数23 为,计算成十进制就是223 = 8388608,所以十进制精度只有6~7位
    • 对于double类型来说,尾数部分52位,计算成十进制就是252=4503599627370496,所以十进制的精度是15~16位
  • 以上几个都是官方的数据
总结
  • 浮点数float和double在内存中是按照科学计数法来存储的,取值范围是由指数的位数来决定的,精度是由尾数的位数来决定的。
浮点数精度/位数符号S指数E扩展范围 (指数的取值范围)最大/小值(取值范围)尾数位M尾数取值范围(精度)
float32bit 单精度1bit(0正1负)8bit-27 ~ 27-1(-128~127)2127(1038级别的数)23bit8388608,7位,精度为6~7位
double64bit双精度1bit(0正1负)11bits-210 ~ 210-1(-1024~1023)21023(10308级别的数)52bit45035_99627_37049_6,16位,精度为15~16位

浮点数和二进制数互相转化

十进制浮点数如何用二进制表示
  • 计算过程,小数部分,将小数部分乘以2,取出结果中整数部分作为二进制表示的第一位(大于等于1为1,小于1 为0),然后在将小数部分乘以2,得到整数部分作为二进制表示第二位…依次类推知道小数部分位0.
  • 特殊情况永远都不会位0:小数部分循环出现,无法停止,则用优先的二进制位无法表示这个小数,这也是在编程语言中小数位出现误差的原因。
  • 我们用如下的案例来说明这个过程10.6:
0.6*2=1.2 ---- 1
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
.
.
.
0.6*2=1.2 ---- 1
0.2*2=0.4 ---- 0
  • 以上我们可以发现0.6 是一个无法精确表示的一个数值,用二进制表示1001 1001 1001 1001 …
  • 那10.6 的二进制我们可以表示如下:1010.1001 1001 1001 …
二进制浮点数如何转为十进制
  • 计算过程:从左到右,v[i]*2(-i), i为从左到右的index,v[i]为该位的值,直接看例子如下:
  • 10.6 的二进制1010.1001 1001 1001,从小数点位为基准如下:
  • 0 * 20 + 1 * 21 + 0 * 2 2 + 1 * 23 = 0+2+0+8//(整数部分)
  • 1 * 2-1 + 0 * 2-2 + 0 * 2-3 + 1 * 2-4 + 1 * 2-5 + 0 * 2-6 + 0 * 2-7 + 1 * 2-8 … = 0.5+0.0625 + 0.03125 ≈ 0.6 // 小数部分

问题解答

  • 我们通过开始的案例,还有之前关于double,float这部分的分析,我们来解答一下最开始的哪些问题是怎么出现的:
float类型赋值给double类型变量出现精度问题
  • 因为float的尾数23为,double尾数52位,所以float类型中保存的0.6 的二进制转成double二进制的时候低位的二进制自动变成0 ,与用double类型保存的0.6的二进制是不一样的,所以出问题来,如下图来解释:
float 类型的0.6f:    
1001 1001 1001 1001 1001 100
double类型的d1 = 0.6d:
1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 
将float类型f 赋值给double类型的d2后,d2 的实际数据位:
1001 1001 1001 1001 1001 1000 0000 0000 0000 0000 0000 0000 0000
  • 如上如果用d2 和d1 比较他们肯定是不相等的
第一个案例分析
System.out.println( 1f == 0.99999999f );
  • 这个结果是true因为计算机无法区分这个两个的二进制数,我们也来推到一些他们的二进制表示
1.0(十进制)↓
00111111 10000000 00000000 00000000(二进制)↓
0x3F800000(十六进制)0.99999999(十进制)↓
00111111 10000000 00000000 00000000(二进制)↓
0x3F800000(十六进制)0.9999999(十进制)↓
00111111 01111111 11111111 11111110(二进制)↓
0x3F7FFFFE(十六进制)
  • 如上,这第一个和第二个二进制数是一样的,第三个是不一样的,只是因为上面的0.99999999f(8个9) 明显超过来float类型的精度范围凑巧和1 是一样的就出现这种问题。
浮点计算
  • Java当中默认声明的小数是double类型的,其默认后缀"d"或"D"可以省略,如果要声明为float类型,需显示添加后缀"f"或"F"
  • 我们尽量用BigDecial来计算

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310606.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net core 集成 sentry 进行异常报警

.net core 集成 sentry 进行异常报警IntroSentry 是一个实时事件日志记录和汇集的平台。其专注于错误监控以及提取一切事后处理所需信息而不依赖于麻烦的用户反馈。它分为客户端和服务端&#xff0c;客户端(目前客户端有 C#, Python, PHP, JavaScript, Ruby等多种语言)就嵌入在…

[Qt入门]QTableWidget控件创建

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);//TableWidget控件//设置列数ui->tableWidget->setColumnCount(3);//设置水平表头ui->tableWidget->…

Zookeeper--Watcher机制源码剖析一

Watcher-- 数据变更通知 我们知道Zookeeper提供来分布式数据的订阅/发布功能&#xff0c;一个典型的发布/订阅模型系统定义了一种一对多的订阅关系&#xff0c;能让多个订阅者同时监听某个主题对象&#xff0c;当这个被监听对象自身状态发生变化时候&#xff0c;会通知所有订阅…

.NET Core 3.1 的REST 和gRPC 性能测试

看到越南小哥 的github 上的Evaluating Performance of REST vs. gRPC &#xff0c; 使用的是.NET Core 3.0 , 今天我把它升级到.NET Core 3.1 同样做了一个测试&#xff0c;文章的结果和他的博客文章是一样的&#xff1a;https://dev.to/thangchung/performance-benchmark-grp…

Zookeeper--Watcher机制源码剖析二

Watcher触发 我们从实际操作时候的表现来看Watcher的触发&#xff0c;比如Zookeeper中NodeDataChanged时间的触发是“Watcher监听的对应数据节点的数据内容发生变更”&#xff0c;需要修改节点数据那么必然和数据节点存储的位置DataTree有关系&#xff0c;我们从这里去寻找修改…

吴军《硅谷来信》工作篇学习总结

【学习总结】| 作者 / Edison Zhou这是恰童鞋骚年的第215篇原创文章2018年在得到App上订阅了吴军老师的《硅谷来信》&#xff0c;从此每天的碎片时间就开始听吴军老师在大洋彼岸寄来的信件了。整个来信涵盖了职业发展、工作效率、业余生活等多个主题&#xff0c;从吴军老师的视…

Zookeeper实践与应用-- Nginx负载均衡差异

Nginx/ZooKeeper 负载均衡的差异 Nginx 是我们常见的反向代理服务器&#xff0c;也被广泛的用作负载均衡服务器ZooKeeper是分布式协调服务框架&#xff0c;有时也被用来做负载均衡 Nginx Nginx负载均衡配置非常简单&#xff0c;吧多个Web Server配置到nginx中&#xff0c;用…

从对我的质疑说起,谈谈Linux下的文件删除

特特本来就是个刚毕业的小菜&#xff0c;很多知识都是靠着大家的指点才慢慢学会的。之前在一篇"纯属虚构"的文章 (鹅厂后台开发工程师的工作日常) 提到使用 rm 命令删除一个近 100 G 的 log 文件。很荣幸&#xff0c;这篇文章被一个大号转载了&#xff0c;获得了很不…

Zookeeper实践与应用--分布式锁实现

分布式锁 分布式锁是控制分布式系统之间同步访问资源的一种方式&#xff0c;如果不同系统是同一个系统的不同主机之间共享一个或一组资源&#xff0c;那么访问这些资源的时候&#xff0c;往往需要通过一些呼哧手段来防止彼此之间的干扰保证统一性&#xff0c;因此需要分布式锁…

关于 Blazor Server Side 的一些杂项, 感想

在2016年, 本人就开始了一个内部项目, 其特点就是用C#构建DOM树, 然后把DOM同步到浏览器中显示. 并且在一些小工程中使用.3年下来, 效果很不错, 但因为是使用C#来构建控件树, 在没有特定语法的情况下, 代码风格不是那么好.典型的风格大概是这样的:这个模式挺好的, 有点嫌弃C#代…

重现江湖!大数据高并发——架构师秘籍

大数据高并发的话题屡见不鲜&#xff0c;各种应对的方式方法也四处可见。然而笔试面试中一问就懵&#xff0c;简直是高薪拦路虎。为什么呢&#xff1f;究其原因&#xff0c;还是思路不清晰&#xff0c;缺乏实操&#xff0c;所以一问就倒。作为专注.Net领域十几年的老司机&#…

[剑指offer]面试题4:替换空格

面试题4&#xff1a;替换空格 题目&#xff1a;请实现一个函数&#xff0c;把字符串中的每个空格替换成"%20"。例如输入“We are happy.”&#xff0c;则输出“We%20are%20happy.”。 ❖ 时间复杂度为O&#xff08;n2&#xff09;的解法&#xff0c;不足以拿到Offer…

Zookeepe实践与应用--分布队列

分布式队列 接触到不少分布式队列的产品&#xff0c;比如&#xff0c;ActiveMq&#xff0c;RocketMQ&#xff0c;kafka等消息中间价&#xff0c;现在我们看看Zookeeper实现的分布式队列。分布式队列简单讲就可以分两个部分&#xff0c;一种是先进先出&#xff0c;另外一种是等…

ASP.NET Core+Quartz.Net实现web定时任务

点击蓝色“Dotnet Plus”关注我哟加个“星标”&#xff0c;每天清晨 07:25&#xff0c;干货推送&#xff01;作为一枚后端程序狗&#xff0c;项目实践常遇到定时任务的工作&#xff0c;最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主…

Redis基础数据结构内部实现简单介绍

5种基础数据结构 Redis有5种基础数据结构&#xff0c;分别是&#xff1a;String&#xff08;字符串&#xff09;&#xff0c;list&#xff08;列表&#xff09;&#xff0c;hash&#xff08;字典&#xff09;&#xff0c;set&#xff08;集合&#xff09;&#xff0c;zset&…

[剑指offer]面试题7:用两个栈实现队列

面试题7&#xff1a;用两个栈实现队列 题目&#xff1a;用两个栈实现一个队列。队列的声明如下&#xff0c;请实现它的两个函数appendTail和deleteHead&#xff0c;分别完成在队列尾部插入结点和在队列头部删除结点的功能。 用两个栈模拟一个队列的操作: 代码如下: #include …

ASP.NET CORE WEBAPI文件下载

最近要使用ASP.NET CORE WEBAPI用来下载文件&#xff0c;使用的.NET CORE 3.1。考虑如下场景&#xff1a;文件是程序生成的。文件应该能兼容各种格式。浏览器可以感知进行下载。准备经过简单的调研&#xff0c;得到以下结论。ASP.NET CORE 提供FileResult这种类型的ActionResul…

Redis高级数据结构原理解析-bitmap,hyperloglog

Redis 位图 开发过程中&#xff0c;我们可能遇到这种场景记录用户的打卡情况&#xff0c;签到情况&#xff0c;这些场景只有两种结果&#xff0c;有或者没有&#xff0c;加入记录的数据量比较大&#xff0c;比如用一年的数据&#xff0c;如果用Redis中普通key/value&#xff0…

[剑指offer]面试题8:旋转数组的最小数字

面试题8&#xff1a;旋转数组的最小数字 题目&#xff1a;把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;我们称之为数组的旋转。输入一个递增排序的数组的一个旋转&#xff0c;输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转&#xff0c;该数…

.NET Core + Kubernetes:快速体验

Kubernetes[1] 是目前非常主流的容器编排工具&#xff0c;在应用创建、应用部署、应用扩容、应用更新等方面都非常的方便&#xff0c;而且在应用故障时&#xff0c;也可以快速自愈。所以基于微服务架构下的产品&#xff0c;了解 Kubernetes 的使用是非常必要的&#xff0c;我猜…