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 ...
随机推荐
- ios真机使用fixed定位页面滚动时fixed定位的元素也会跟着滚动
到了ios真机APP中,页面向下滚动,fixed的元素也跟着滚,虽然最后它还是到了它该在的地方,但是它跟着滚动也很影响页面的流畅性和交互性好伐.
- 12_Sensor简单实例
列出Android手机所支持的Sensor. package com.example.sensorlist; import java.util.List; import android.app.Act ...
- 领域设计:Entity与VO
本文探讨如下内容: 什么是状态 什么是标识 什么是Entity 什么是VO(ValueObject) 在设计中如何识别Entity和VO 要理解Entity和VO,需要先理解两个概念:「状态」和「标识 ...
- ubantu+nginx+uwsgi+django部署
1.更新ubantu的apt apt-get update 必要时候更新系统: apt-get upgrade 2.远程连接服务器 ssh 用户名@ip 上传代码 : scp ...
- springboot多模块项目搭建遇到的问题记录
废话不多说,直接上问题报错与解决方法. 问题报错一:(报错信息看下方代码) 问题原因:'com.company.logistics.service.company.CompanyService' 未找 ...
- 图像处理术语解释:灰度、色相、饱和度、亮度、明度、阿尔法通道、HSL、HSV、RGBA、ARGB和PRGBA以及Premultiplied Alpha(Alpha预乘)等基础概念详解
☞ ░ 前往老猿Python博文目录 ░ 一.引言 由于老猿以前没接触过图像处理,在阅读moviepy代码时,对类的有些处理方法代码看不懂是什么含义,为此花了4天时间查阅了大量资料,并加以自己的理解和 ...
- PyQt(Python+Qt)学习随笔:QTableView的showGrid属性
老猿Python博文目录 老猿Python博客地址 showGrid属性用于控制视图中数据项之间是否显示网格,如果该属性为True,则绘制网格:如果该属性为False,则不绘制网格. showGrid ...
- PyQt(Python+Qt)学习随笔:窗口部件大小策略sizePolicy与SizeConstraint布局大小约束的关系
在<PyQt(Python+Qt)学习随笔:Qt Designer中部件的三个属性sizeHint缺省尺寸.minimumSizeHint建议最小尺寸和minimumSize最小尺寸>. ...
- XJOI contest 1592
首先 热烈庆祝"CSP-S 2020全国开放赛前冲刺模拟训练题2"圆满结束!!! 感谢大毒瘤颗粒囊的题目.题目还是很不错的,部分分设置的不合理,各种神仙随便 AK ,蒟蒻只能爆零. ...
- 【学习笔记】使用 bitset 求解较高维偏序问题
求解五维偏序 给定 \(n(\le 3\times 10^4)\) 个五元组,对于每个五元组 \((a_i, b_i, c_i, d_i, e_i)\),求存在多少个 \(1\le j\le n\) ...