基本数据类型占用内存大小

最近项目中修复了一个关于类型转换精度丢失的问题,以前对于类型转换会丢失精度只知其然,不知其所以然,这次了解了下相关原理,也分享给大家。先来回顾一下 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 类型转换精度问题的更多相关文章

  1. java类型转换

    //java类型转换public class Demo2 { public static void main(String[] args){ int num1 = 55; int num2 =77; ...

  2. Java 浮点数精度丢失

    Java 浮点数精度丢失 问题引入 昨天帮室友写一个模拟发红包抢红包的程序时,对金额统一使用的 double 来建模,结果发现在实际运行时程序的结果在数值上总是有细微的误差,程序运行的截图: 输入依次 ...

  3. Java Double 精度问题总结

    package Demo_1.Test_2; import java.math.BigDecimal; /** * @描述:Java Double 精度问题总结 * @详细描述:使用Java,doub ...

  4. Java类型转换详解

    Java类型转换详解 最近有同学问:自动类型转换老是记不住,到底是大转小,还是小转大 其实这个不用死记硬背,很好理解,我们拿 int 和 short 来举例: int 是 4 字节,也就是 32 bi ...

  5. Java 类型转换以及Object转成其他类型

    Object转int int count=(int)map.get("count") int count=Integer.parseInt((String)map.get(&quo ...

  6. java 类型转换:

    数值数据类型: 1.自动类型转换 byte->short ->int->long-->float--->double 范转小的类型向范围大的类型号转换,由系统自动完成   ...

  7. java 类型转换(摘自网络)

    java基本类型转换规则   1.基本数据类型的转换是指由系统根据转换规则自动完成,不需要程序员明确地声明不同数据类型之间的转换.     转换在编译器执行,而不是等到运行期再执行. 2.基本数据类型 ...

  8. SQLServer类型与Java类型转换问题解决

    ResultSet 接口提供用于从当前行获取列值的获取 方法(getBoolean.getLong 等).可以使用列的索引编号或列的名称获取值.一般情况下,使用列索引较为高效.列从 1 开始编号.为了 ...

  9. java类型转换详解(自动转换和强制转换)

    自动转换 class Hello { public static void main(String[] args) { //自动转换 int a = 5; byte b = 6; int c = a ...

随机推荐

  1. mysql一条sql语句如何执行的?

    mysql 一条sql语句如何执行的? 文章内容源自:极客时间-林晓彬老师-MySQL实战45讲 学习整理 在了解一条查询语句如何执行之前,需要了解下MySQL的基本架构是怎样的,如下图所示: 可以看 ...

  2. 关于 [栈溢出后jmp esp执行shellcode] 原理分析

    原文地址:https://blog.csdn.net/lixiangminghate/article/details/53333710 正常情况下,函数栈分布图如下: 即,返回地址被改为一段缓存区的地 ...

  3. java并发编程实战《六》等待-通知机制

    用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...

  4. 第三十四章、PyQt中的输入部件:QComboBox组合框功能详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.概述 Designer中输入工具部件中的Combo Box组合框与 ...

  5. Python正则表达式re.search(r'\*{3,8}','*****')和re.search('\*{3,8}','*****')的匹配结果为什么相同?

    老猿做过如下测试: >>> re.search(r'\*{3,100}','*****') <re.Match object; span=(0, 5), match='**** ...

  6. Python爬虫学习遇到的问题

    老猿在学习Python中爬虫知识时遇到了如下问题: 爬取网页内容后写入文件报错UnicodeEncodeError: 'gbk' codec can't encode的问题解决方案 urllib.re ...

  7. PyQt学习随笔:重写组件的event方法捕获组件的事件

    在PyQt的组件对象中,都有从QWidget中继承的方法event,而QWidget.event是对QObject类定义的虚拟方法event的实现. event方法的语法: bool event(QE ...

  8. WebRequest抓取网页数据出现乱码问题

    今天项目里突然有个功能用不起来了,本机确实好的 ,这个很无语 不知道为啥 经过写日志发现html 变成了这样的东西,很是头疼,刚开始各种编码转换,发现这并不是编码的问题 后面观察目标网站多了一个gzi ...

  9. 从go-libp2p开始

    这里是从一系列关于libp2p的go实现教程开始,go-libp2p 我们会讲述go的安装,go模块的设置,启动libp2p节点,并在它们之间发送消息. 安装go go-libp2p推荐使用包含 mo ...

  10. sort by背后使用了什么排序算法

    用到了快速排序,但不仅仅只用了快速排序,还结合了插入排序和堆排序 搬运自https://blog.csdn.net/qq_35440678/article/details/80147601