老大说:谁要再用double定义商品金额,就自己收拾东西走


先看现象

涉及诸如float或者double这两种浮点型数据的处理时,偶尔总会有一些怪怪的现象,不知道大家注意过没,举几个常见的栗子:

典型现象(一):条件判断超预期

System.out.println( 1f == 0.9999999f );   // 打印:false
System.out.println( 1f == 0.99999999f );  // 打印:true    纳尼?

典型现象(二):数据转换超预期

float f = 1.1f;
double d = (double) f;
System.out.println(f);  // 打印:1.1
System.out.println(d);  // 打印:1.100000023841858  纳尼?

典型现象(三):基本运算超预期

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   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?
//    打印:8.4552632E7   纳尼?不是 +1了吗?

看到没,这些简单场景下的使用情况都很难满足我们的需求,所以说用浮点数(包括doublefloat)处理问题有非常多隐晦的坑在等着咱们!

怪不得技术总监发狠话:谁要是敢在处理诸如 商品金额订单交易、以及货币计算时用浮点型数据(double/float),直接让我们走人!


原因出在哪里?

我们就以第一个典型现象为例来分析一下:

System.out.println( 1f == 0.99999999f );

直接用代码去比较10.99999999,居然打印出true

这说明了什么?这说明了计算机压根区分不出来这两个数。这是为什么呢?

我们不妨来简单思考一下:

我们知道输入的这两个浮点数只是我们人类肉眼所看到的具体数值,是我们通常所理解的十进制数,但是计算机底层在计算时可不是按照十进制来计算的,学过基本计组原理的都知道,计算机底层最终都是基于像010100100100110011011这种01二进制来完成的。

所以为了搞懂实际情况,我们应该将这两个十进制浮点数转化到二进制空间来看一看。

十进制浮点数转二进制 怎么转、怎么计算,我想这应该属于基础计算机进制转换常识,在 《计算机组成原理》 类似的课上肯定学过了,咱就不在此赘述了,直接给出结果(把它转换到IEEE 754 Single precision 32-bit,也就float类型对应的精度)

1.0(十进制)↓
00111111 10000000 00000000 00000000(二进制)↓
0x3F800000(十六进制)
0.99999999(十进制)↓
00111111 10000000 00000000 00000000(二进制)↓
0x3F800000(十六进制)

果不其然,这两个十进制浮点数的底层二进制表示是一毛一样的,怪不得==的判断结果返回true

但是1f == 0.9999999f返回的结果是符合预期的,打印false,我们也把它们转换到二进制模式下看看情况:

1.0(十进制)↓
00111111 10000000 00000000 00000000(二进制)↓
0x3F800000(十六进制)
0.9999999(十进制)↓
00111111 01111111 11111111 11111110(二进制)↓
0x3F7FFFFE(十六进制)

哦,很明显,它俩的二进制数字表示确实不一样,这是理所应当的结果。

那么为什么0.99999999的底层二进制表示竟然是:00111111 10000000 00000000 00000000呢?

这不明明是浮点数1.0的二进制表示吗?

这就要谈一下浮点数的精度问题了。


浮点数的精度问题!

学过 《计算机组成原理》 这门课的小伙伴应该都知道,浮点数在计算机中的存储方式遵循IEEE 754 浮点数计数标准,可以用科学计数法表示为:

只要给出:符号(S)阶码部分(E)尾数部分(M) 这三个维度的信息,一个浮点数的表示就完全确定下来了,所以floatdouble这两种浮点数在内存中的存储结构如下所示:

1、符号部分(S)

0-正  1-负

2、阶码部分(E)(指数部分)

  • 对于float型浮点数,指数部分8位,考虑可正可负,因此可以表示的指数范围为-127 ~ 128

  • 对于double型浮点数,指数部分11位,考虑可正可负,因此可以表示的指数范围为-1023 ~ 1024

3、尾数部分(M)

浮点数的精度是由尾数的位数来决定的:

  • 对于float型浮点数,尾数部分23位,换算成十进制就是 2^23=8388608,所以十进制精度只有6 ~ 7位;

  • 对于double型浮点数,尾数部分52位,换算成十进制就是 2^52 = 4503599627370496,所以十进制精度只有15 ~ 16

所以对于上面的数值0.99999999f,很明显已经超过了float型浮点数据的精度范围,出问题也是在所难免的。


精度问题如何解决

所以如果涉及商品金额交易值货币计算等这种对精度要求很高的场景该怎么办呢?

方法一:用字符串或者数组解决多位数问题

校招刷过算法题的小伙伴们应该都知道,用字符串或者数组表示大数是一个典型的解题思路。

比如经典面试题:编写两个任意位数大数的加法、减法、乘法等运算

这时候我们我们可以用字符串或者数组来表示这种大数,然后按照四则运算的规则来手动模拟出具体计算过程,中间还需要考虑各种诸如:进位借位符号等等问题的处理,确实十分复杂,本文不做赘述。

方法二:Java的大数类是个好东西

JDK早已为我们考虑到了浮点数的计算精度问题,因此提供了专用于高精度数值计算的大数类来方便我们使用。

在前文《不瞒你说,我最近跟Java源码杠上了》中说过,Java的大数类位于java.math包下:

可以看到,常用的BigInteger 和 BigDecimal就是处理高精度数值计算的利器。

BigDecimal num3 = new BigDecimal( Double.toString( 1.0f ) );
BigDecimal num4 = new BigDecimal( Double.toString( 0.99999999f ) );
System.out.println( num3 == num4 );  // 打印 falseBigDecimal num1 = new BigDecimal( Double.toString( 0.2 ) );
BigDecimal num2 = new BigDecimal( Double.toString( 0.7 ) );// 加
System.out.println( num1.add( num2 ) );  // 打印:0.9// 减
System.out.println( num2.subtract( num1 ) );  // 打印:0.5// 乘
System.out.println( num1.multiply( num2 ) );  // 打印:0.14// 除
System.out.println( num2.divide( num1 ) );  // 打印:3.5

当然了,像BigInteger 和 BigDecimal这种大数类的运算效率肯定是不如原生类型效率高,代价还是比较昂贵的,是否选用需要根据实际场景来评估。


END

史上最全的延迟任务实现方式汇总!附代码(强烈推荐)

除了负载均衡,Nginx 还能干啥?

震惊!这样终止线程,竟然会导致服务宕机?

关注公众号发送”进群“,老王拉你进读者群。

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

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

相关文章

用python做采集时相对路径转换成绝对路径

采集时&#xff0c;有时候需要采集图片&#xff0c;但某些网站的图片提供的相对地址&#xff0c;最好转换成绝对地址在scrapy中有如下的解决策略http://stackoverflow.com/questions/6499603/python-scrapy-convert-relative-paths-to-absolute-paths http://stackoverflow.com…

图片一键调整工具V1.0-免费版

一、工具介绍 这是博主自己开发的图片一键调整工具V1.0,它可以调整图片宽度和高度、压缩图片大小、改变图片背景、转换图片格式和图片透明化&#xff0c;都是很常用的功能。操作起来简单方便。 二、工具操作 1.调整图片背景 首先&#xff0c;把该工具软件和图片放到同一文件…

一口气说出 9种 分布式ID生成方式,面试官有点懵了

写在前边前两天公众号有个粉丝给我留言吐槽最近面试&#xff1a;“年前我在公司受点委屈一冲动就裸辞了&#xff0c;然后现在疫情严重两个多月还没找到工作&#xff0c;接了几个视频面试也都没下文。好多面试官问完一个问题&#xff0c;紧接着说还会其他解决方法吗&#xff1f;…

PHP_正则_获取图片所有属性

2019独角兽企业重金招聘Python工程师标准>>> <?php /*PHP正则提取图片img标记中的任意属性*/ $str <center><img src"/uploads/images/20100516000.jpg" height"120" width"120"><br />PHP正则提取或更改图片…

matlab拔河比赛_拔河比赛

matlab拔河比赛Description: 描述&#xff1a; This is a standard interview problem to divide a set of number to two different set where both the subset contains same number of element and have a minimum difference of sum between them using backtracking. 这是…

别再问我 new 字符串创建了几个对象了!我来证明给你看!

如何证明 new String 创建了 N 个对象&#xff1f; 我想所有 Java 程序员都曾被这个 new String 的问题困扰过&#xff0c;这是一道高频的 Java 面试题&#xff0c;但可惜的是网上众说纷纭&#xff0c;竟然找不到标准的答案。有人说创建了 1 个对象&#xff0c;也有人说创建了…

C#使用Sockets操作FTP【转载】

using System; using System.Collections; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions;/* *解析drwxr-xr-x *第一位表示文件类型。d是目录文件&#xff0c;l是链接文件&#xff0c;-是普通文件&a…

scala 字符串占位符_如何在Scala中将带有换行符的字符串转换为字符串列表?

scala 字符串占位符A string is a sequence of characters and it can contain multiple lines, for this, the string uses the newline character \n. And, we can separate each newline into a string and store them as a list of strings. 字符串是字符序列&#xff0c;…

一款开源免费的SSH/SFTP客户端Electerm,同时支持Linux、MacOS、Windows

简介&#xff1a; Electerm是一个跨平台的Terminal/SSH/SFTP客户端工具&#xff0c;同时支持Linux、MacOS、Windows&#xff0c;基于electron/ssh2/node-pty/xterm/antd/useProxy等开源组件。 下载地址&#xff1a; 官网下载&#xff1a;https://electerm.html5beta.com/ Git…

用了自定义Banner后,SpringBoot瞬间变的高大上了...

Spring Boot 在启动的时候&#xff0c;我们或许想要把自己公司的 logo&#xff0c;或者是项目的 logo 放上去&#xff0c;我们可以试试本文的这些方法&#xff0c;可以让你快速制作一些 Spring Boot 项目启动时的彩蛋&#xff0c;以提高项目的辨识度&#xff0c;或者是纯碎为了…

菜鸟学前端--javascript基础

在学习过css相关的知识&#xff0c;有了前端工程师的一些基础知识。但要较好的掌握前端&#xff0c;必须要学习好javascript的知识。下面将从基本语法、变量、关键字、保留字、语句、函数、BOM等角度阐释。一、基本语法javacript作为一种面向对象的、脚本级的轻量语言&#xff…

Java LinkedList void add(int index,Object o)方法,带示例

LinkedList void add(int index&#xff0c;Object o)方法 (LinkedList void add(int index, Object o) method) This method is available in package java.util.Collection and here, Collection is an interface. 该方法在java.util.Collection包中可用&#xff0c;在这里&a…

如何生成高性能的短链接?

前言今天&#xff0c;我们来谈谈如何设计一个高性能短链系统&#xff0c;短链系统设计看起来很简单&#xff0c;但每个点都能展开很多知识点&#xff0c;也是在面试中非常适合考察侯选人的一道设计题&#xff0c;本文将会结合我们生产上稳定运行两年之久的高性能短链系统给大家…

iOS 技术官方 QA

2019独角兽企业重金招聘Python工程师标准>>> Q: 在静态库中使用catagory分类运行时提示"selector not recognized" A: 需要配置下project/target属性 Q: 在iOS7以后怎么截图 A: iOS7 提供了相关API实现截图功能&#xff0c;如:-drawViewHierarchyInRect:a…

Python日历模块| weekheader()方法与示例

Python calendar.weekheader()方法 (Python calendar.weekheader() Method) weekheader() method is an inbuilt method of the calendar module in Python. It works on simple text calendars and returns a header containing the abbreviated names of the weekday. weekhe…

IPsec IKEv2(HCIP)

目录 一、IKE介绍 1、IKE介绍 2、IKE的主要作用 3、IKE与IPsec关系 二、IKE基础内容 1、IEK的身份认证方法 数据源认证 预共享密钥PSK 数字证书 数字信封 EAP(IKEv2支持) 数字证书CA如何实现身份认证? 2、IKEv1介绍 IKEv1介绍 IKEv1第一阶段介绍 IKEv1第二阶段…

9个小技巧让你的 if else看起来更优雅

if else 是我们写代码时&#xff0c;使用频率最高的关键词之一&#xff0c;然而有时过多的 if else 会让我们感到脑壳疼&#xff0c;例如下面这个伪代码&#xff1a; 是不是很奔溃&#xff1f;虽然他是伪代码&#xff0c;并且看起来也很夸张&#xff0c;但在现实中&#xff0c;…

poj 3254 状压dp

E -Corn FieldsTime Limit:2000MS Memory Limit:65536KB 64bit IO Format:%I64d & %I64u SubmitStatus Practice POJ 3254Description Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels…

第一弹!安利10个让你爽到爆的IDEA必备插件!

大家好&#xff0c;此篇文章中我会介绍10个非常不错的IDEA插件以及它们常见功能的使用方法。这一期内容搞 Gif 动态图花了很久&#xff0c;很多Gif图片上传到微信还提示过大&#xff0c;所以很多地方重新又录制了一遍Gif图。概览&#xff1a;IDE Features Trainer—IDEA交互式教…

C程序对整数中设置为1的位数进行计数

Problem statement: Write a C program to count number of bits set to 1 in an Integer. 问题陈述&#xff1a;编写一个C程序来计算Integer中设置为1的位数 。 Solution: We can use bitwise operator here to solve the problem. 解决方案&#xff1a;我们可以在这里使用按…