Java 类型转换精度问题
基本数据类型占用内存大小
最近项目中修复了一个关于类型转换精度丢失的问题,以前对于类型转换会丢失精度只知其然,不知其所以然,这次了解了下相关原理,也分享给大家。先来回顾一下 Java 的基本数据类型中整型与浮点型及其所占用的内存大小:
整型:
- int:4 字节 32 位
- long:8 字节 64 位
浮点型:
- float:4 字节 32 位
- double:8 字节 64 位
Java 运算时,当两个不同类型的数进行基本运算符操作时,低精度会自动向高精度转换,字节短的会自动向字节长的转换。
《Java 核心技术》一书中这么归纳到:
如果两个操作数其中有一个是 double 类型,另一个操作就会转换为 double 类型。
否则,如果其中一个操作数是 float 类型,另一个将会转换为 float 类型。
否则,如果其中一个操作数是 long 类型,另一个会转换为 long 类型。
否则,两个操作数都转换为 int 类型。
需要注意 Java 自动转换类型可能会带来精度的丢失,附上一张不会丢失精度的合法类型转换说明图:

图中实现箭头类型转换代表不会丢失精度,虚线箭头类型转换可能会丢失精度。
基本数据类型表示范围
精度和数据类型可表示的数值大小范围息息相关,计算机中所有数值归根到底都是使用二进制 0、1 来组成,因此一个数据类型所占用的内存大小越大,就意味着可用的二进制位数越多,当然可表示的范围就越大。回顾一下几个常见的参与运算的基本数据类型的取值范围:
int
二进制位数:32
最小值:Integer.MIN_VALUE= -2147483648 (-2 的 31 次方)
最大值:Integer.MAX_VALUE= 2147483647 (2 的 31 次方 -1)
long
二进制位数:64
最小值:Long.MIN_VALUE=-9223372036854775808 (-2 的 63 次方)
最大值:Long.MAX_VALUE=9223372036854775807 (2 的 63 次方 -1)
float
二进制位数:32
最小值:Float.MIN_VALUE=1.4E-45 (2 的 -149 次方)
最大值:Float.MAX_VALUE=3.4028235E38 (2 的 128 次方 -1)
double
二进制位数:64
最小值:Double.MIN_VALUE=4.9E-324 (2 的 -1074 次方)
最大值:Double.MAX_VALUE=1.7976931348623157E308 (2 的 1024 次方 -1)
当 long 类型的数大于 Integer.MAX_VALUE 时,long 强制转换 int,就会出现丢失精度。转换过程是将 long 类型数值的二进制数从低位到高位截取 32 位,再将 32 位二进制数转为 int。
long l3 = 24696061952L; //10111000000000000000000000000000000
int c3 = (int)l3; //-1073741824
System.out.println(Integer.toBinaryString(c3)); //1000000000000000000000000000000
上面的例子中,long 类型截取 32 位后转为 int,最高位作为符号位,1 代表负数,强转后的 int 值为 -1073741824。
类似这种不合理的强制转换丢失的已经不仅仅是精度了。
不知道有没有人注意到,long 类型的二进制位数是 64,float 类型的二进制位数是 32,但是 float 类型可表示范围却远远大于 long 类型。更不用提一样是 32 位的 int 了,float 到底啥家庭啊?谜底就在内存结构中。
浮点类型数值的内存结构
与整形类型的内存结构不同,float 在内存中是这样的:
SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
- S:最高位 S 代表符号位
- E:后面 8 位 E 代表指数域,二进制中就是 2 的 n 次方,采用移位存储(127+指数)的二进制方式。
- M:剩下的 23 位 M 代表小数域。规定小数点前的数必须为 1,因此只记录小数点后的数。(从左往右,低位补零)
以 7.8125 为例,整数十进制转二进制,除 2 取余,逆序排列,求得 7 二进制为 111。小数十进制转二进制,乘 2 取整,顺序排列,求得 0.8125 二进制为:0.1101,组合起来是 111.1101。
根据规范,小数点前的数只保留 1,因此将 111.1101 小数点左移两位得 1.111101 * 2^2。
符号位 0,指数位为 2+127=129,即二进制 10000001,小数域为 111101。因此 float 数 7.8125 在内存中存储的格式为:0 10000001 111101 低位补零补齐到 32 位,得:0100 0000 1111 1010 0000 0000 0000 0000。
可以使用 Java 提供的 API 验证一下:
int i = Float.floatToIntBits(7.8125F); //得到 7.8125F 底层数据(十进制)
Integer.toBinaryString(i); //得到指定 int 值的二进制数
//输出 1000000111110100000000000000000
//补上最高位符号位 0,结果与上面计算的一样。
通过对浮点类型数值内存结构的了解,我们知道了 float 虽然可用于存储数值的位数没有 long 型多,但是 float 通过使用指数进行降维打击,可表示范围蹭蹭蹭往上涨。
double 的内存结构同理,只不过 double 二进制位数更多,总共 64 位分别分配给:符号位 1 位,指数位 11 位,小数位 52 位。
需要注意的是,虽然 float 因为有指数的概念,可表示范围变大了,但是其用于存储小数的位数却只有 23 位。这就意味着当一个整型类型数值的二进制位大于 24 位时,类型转换到 float 就会带来精度丢失了。
整型转换浮点型的精度丢失问题
看到上图中的int 转 float、long 转 float 都是虚线表示,代表运算时自动类型转换可能会出现精度丢失的问题。经过上面对浮点型数据内存结构的学习,我们应该不难理解,float 能表示的数的大小靠指数位,但是表示的数的精度需要靠小数位。而 float 的小数位只有 23 位,而 int 是 32 位。
举个例子:int 值 16777217,二进制数 1 0000 0000 0000 0000 0000 0001,除去最高位符号位后,需要 25 位表示。
顺带提一下,计算某个数值除了符号位外需要多少位二进制位可以表示,除了挨个去数二进制数外,还可以直接计算 log2 的值:
int i = 16777217;
double num = Math.log(i) / Math.log(2.0);
//num = 24.000000085991324,即需要 25 位二进制位表示
int 转 float,转换过程是先将 int 的数值由十进制转为二进制,再通过对二进制数左移小数点直到个位为 1,变为:1. 0000 0000 0000 0000 0000 0001 * 2 ^ 24,转换后的数小数点后有 24 位,对 float 来说只能舍弃掉无法表示的位数,只保留 23 位小数位,指数位 24 + 127 = 151,二进制为 10010111,因此转换后的 float 二进制数为 110010111 + 23个0,float 值为 1.6777216E7,已经丢失了精度。
同理,int 转 double,由于 double 有 52 位小数位,因此足以 hold 住 int 的精度,而 long 需要 64 位表示精度,因此 long 转 double 也可能出现精度丢失。另外需要注意的是,单位秒的时间戳,也需要 31 位来表示,用 int 表示是够的,但是转 float 也一样会丢失精度。
以上就是对 Java 类型转换精度问题的分析,希望对你有帮助
Java 类型转换精度问题的更多相关文章
- java类型转换
//java类型转换public class Demo2 { public static void main(String[] args){ int num1 = 55; int num2 =77; ...
- Java 浮点数精度丢失
Java 浮点数精度丢失 问题引入 昨天帮室友写一个模拟发红包抢红包的程序时,对金额统一使用的 double 来建模,结果发现在实际运行时程序的结果在数值上总是有细微的误差,程序运行的截图: 输入依次 ...
- Java Double 精度问题总结
package Demo_1.Test_2; import java.math.BigDecimal; /** * @描述:Java Double 精度问题总结 * @详细描述:使用Java,doub ...
- Java类型转换详解
Java类型转换详解 最近有同学问:自动类型转换老是记不住,到底是大转小,还是小转大 其实这个不用死记硬背,很好理解,我们拿 int 和 short 来举例: int 是 4 字节,也就是 32 bi ...
- Java 类型转换以及Object转成其他类型
Object转int int count=(int)map.get("count") int count=Integer.parseInt((String)map.get(&quo ...
- java 类型转换:
数值数据类型: 1.自动类型转换 byte->short ->int->long-->float--->double 范转小的类型向范围大的类型号转换,由系统自动完成 ...
- java 类型转换(摘自网络)
java基本类型转换规则 1.基本数据类型的转换是指由系统根据转换规则自动完成,不需要程序员明确地声明不同数据类型之间的转换. 转换在编译器执行,而不是等到运行期再执行. 2.基本数据类型 ...
- SQLServer类型与Java类型转换问题解决
ResultSet 接口提供用于从当前行获取列值的获取 方法(getBoolean.getLong 等).可以使用列的索引编号或列的名称获取值.一般情况下,使用列索引较为高效.列从 1 开始编号.为了 ...
- java类型转换详解(自动转换和强制转换)
自动转换 class Hello { public static void main(String[] args) { //自动转换 int a = 5; byte b = 6; int c = a ...
随机推荐
- day101:MoFang:模型构造器ModelSchema&注册功能之手机号唯一验证/保存用户注册信息/发送短信验证码
目录 1.模型构造器:ModelSchema 1.SQLAlchemySchema 2.SQLAlchemyAutoSchema 2.注册功能基本实现 1.关于手机号码的唯一性验证 2.保存用户注册信 ...
- 【MMT】ICLR 2020: MMT(Mutual Mean-Teaching)方法,无监督域适应在Person Re-ID上性能再创新高
原文链接 小样本学习与智能前沿 . 在这个公众号后台回复"200708",即可获得课件电子资源. 为了减轻噪音伪标签的影响,文章提出了一种无监督的MMT(Mutual Mean-T ...
- springmvc跨域问题
1.跨域问题: 按照网上所有的方法试了一遍,都没跨过去,正在无助之际,使用filter按照下面的方法解决的时候出现了转机: 添加filter: package com.thc.bpm.filter; ...
- Abp(net core)+easyui+efcore实现仓储管理系统——出库管理之七(五十六)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统--ABP总体介绍(一) abp(net core)+ ...
- print(end="\r") 滚动输出到屏幕
for i in range(10000): print(i, end = "\r") print(end="\r") 滚动输出到屏幕
- 0、tensorflow学习开始
结合tensorflowtf 2.x , tensorflow 1.x, pytorch来深入理解深度学习架构,用博客来记录这一系列,日后也方便回顾,博客中也会加入个人理解和感悟 参考的博客列表如下: ...
- 【Alpha冲刺阶段】Scrum Meeting Daily1
1.会议简述 会议开展时间 2020/5/22 8:30-9:00 PM 会议基本内容摘要 讨论了基础的分工,以及明确了各自模块需要完成的任务 参与讨论人员 全体参与 特别说明 会议需要每天都开展!! ...
- 差分约束系统——POJ1275
之前做过差分,但是没做过差分约束系统. 正好在学军机房听课讲到这道题,就顺带学了一下. 其实...就是列不等式组然后建图 作为蒟蒻,当然是不会加二分优化的啦...但是poj上还是94ms跑过了qwq ...
- HuangB2ydjm
Hi! 我现在呢还是学生,想想初中的同学都结婚生子了,自己,嗯.(啊!!!) 本科以及硕士读的都是应用统计 在这里请广大网友多多指教了! 如果有机会的话,大家可以一起造轮子 you can catch ...
- MySQL事务(二)事务隔离的实现原理:一致性读
今天我们来学习一下MySQL的事务隔离是如何实现的.如果你对事务以及事务隔离级别还不太了解的话,这里左转. 好的,下面正式进入主题.事务隔离级别有4种:读未提交.读提交.可重复读和串行化.首先我们来说 ...