【从入门到放弃-Java】码出高效-计算机基础-二进制和浮点数

简介: - Java - 码出高效 - 浮点数 - 二进制

前言

最近在学习孤尽大神内网分享的资料时,被安利了一本书【码出高效】,这也是他在【阿里巴巴Java开发手册】一书后,结合阿里的生产经验和历史故障给出的最佳研发实践。

刚拿到书,打开目录,会觉得这是一本基础入门书,里面的东西大家都懂,但是没翻几页你就会被里面生动的案例和技术细节吸引,有很多在日常开发中会经常用到,但一直被忽略的点,这些点往往是引发故障的点。现在就将我在学习过程中的笔记和收获记录下来和大家一起分享。

本篇就从计算机入门的二进制和浮点数开始。

二进制

起源

简单来说,计算机是由晶体管和电路板组合起来的电子设备,信息存储和逻辑计算的元数据归根结底都是0和1的信号处理。

只有0和1,进位规则是逢二进一,借位规则是借一当二,这就是二进制。

编码方式

通过符号位和数字实际值可以表示数,有以下三种基本编码方式

原码

正数部分是数值本身,符号位为0;负数数值部分是数值本身,符合位为1。八位二进制数表示范围是[-127, 127]。这是最符合人类认知的编码方式。

反码

正数部分是数值本身,符号位为0;负数数值部分是正数的基础上对各位取反,符号位为1。八位二进制的表示范围是[-127, 127]。

补码

正数部分是数值本身,符号位为0;负数树值部分是正数的基础上对各位取反后加一,符号位为1.八位二进制的表示范围是[-128, 127]。

加减运算

因为计算机中只有加法器,没有减法器。通过原码相加,结果会出错。如:

1 - 2 = 1 + ( -2 ) = -1

通过原码计算为[0000 0001] + [1000 0010] = [1000 0011] = -3,结果明显是不对的。

通过反码计算为[0000 0001] + [1111 1101] = [1111 1110] = -1,结果正确。

但是反码在某些情况出现新的问题。如:

2 - 2 = 2 + ( -2 ) = 0

通过原码计算为[0000 0010] + [1000 0010] = [1000 0100] = -4,结果不正确。

通过反码计算为[0000 0010] + [1111 1101] = [1111 1111] = -0,反码有+0和-0的区分,用反码计算也是有歧义的。

通过补码计算为[0000 0010] + [1111 1110] = [0000 0000] = 0,补码中只有0,结果符合预期。

加减法是一个高频运算,使用同一个运算器,可以减少中间变量的存储及转换成本,也降低了CPU设计的复杂度。

位运算

左移:<<,相当于乘2

右移:>>,相当于除2(最后一位是奇数时,存在误差),带符号位右移动,负数最高位补1,正数最高位补0。所以可以通过使用 ((b >> 31) ^ (a >> 31)) == 0 来判断两个整数正负是否相同。

无符号右移:>>>,无符号位右移,正数和负数最高位都补0,主要用于一些数据转换,如加密、压缩、影音编码等场景。

取反:~,按位取反,0取反是1

与:&,按位与,相同位都为1才,就为1

或:|,按位或,相同位只要有一个是1,就为1

异或:^,按位异或,相同位相等的时候为1,不相同的时候为0

浮点数

浮点数采用科学技术法来表示的,由符号位、有效数字、指数三部分组成。但编码过程中经常会出现丢失精度的问题,如下:

float a = 1f;
float b = 0.9f;
float c = (a - b);
//c = 0.100000024
System.out.println(c);

常见的浮点数有单精度和双精度浮点数,占用字节数和取值范围不同。单精度四个字节,双精度八个字节。

单精度浮点数的构成是1位符号位,8位阶码位(指数),23位尾数位(有效数字)。

阶码使用移码来表示,尾数使用原码来表示。移码范围是[0, 255]去掉特殊的0(计算机认为全零是机器0)和255(计算机认为是无穷大),阶码的取值范围是[1, 254],根据移码的定义,[x]=x+2^(n-1),n是8,所以x的取值范围是[-126, 127]

尾数位是原码表示的,1.111....111(23个1), 1 <= a < 2, 因此规格化后的尾数首个1会被省略,因此实际能表示24位尾数,最大值无限接近于2,因此单精度浮点数能表示的最大值为2^127,约等于(1.7*10^38)。

加减运算

小数加减需要将小数点对齐后进行同位相加减,因此浮点数加减需要先将指数对齐,再进行同位加减

零值检测

参与运算的两个数中,只要有一个是0就直接得出结果。因为浮点数运算过程比较复杂

对阶操作

小数点需要对齐,当阶码大小不相等时,需要先对阶,通过移动尾数改变阶码大小,尾数向右移一位,阶码值加一,反之减一。移动尾数过程中可能存在部分二进制被移出。但如果向左移动,会使高位移出,误差更大,因此规定在对阶时,选择阶码小的数进行操作

尾数求和

对接完成后,按位相加即可,如9.8*10^38 + 6.5*10^37 = 9.8*10^38 + 0.65*10^38 = 10.45^38

结果格式化

求和完毕后,如果整数为不在[1, 9],则需要左右调整,尾数向右移动称为右规,向左移动称为左规。10.45^38要调整为1.045^39

结果舍入

因为对接或右规时,尾数需要移动,移出的位会被丢弃,为了减少精度损失,需要先将移出的这部分数据保存出来,称为保护位,格式化后再根据保护位进行舍入处理。

示例

1.0 - 0.9

1.0 - 0.9 = 1.0 + (-0.9)
1.0的浮点数标识

二进制表示

1.0:  [0011 1111 1000 0000 0000 0000 0000 0000]
-0.9: [1011 1111 0110 0110 0110 0110 0110 0110]

对阶

1.0的阶码是127,-0.9的阶码是126,比较阶码后需要向右移动-0.9位数的补码,使其变成127,最高位补1。

-0.9的尾数位补码为:[0001 1001 1001 1001 1001 1010]
对阶后为:[1000 1100 1100 1100 1100 1101]

尾数求和

尾数转换成补码相加

 [1000 1100 1100 1100 1100 1101]
+[1000 0000 0000 0000 0000 0000]
=[0000 1100 1100 1100 1100 1101]

规格化

尾数的最高位必须是1,所以需要将结果向左移动4位,阶码减4。

符号位为1

移动后阶码等于123(二进制[1111011])

尾数为[1100 1100 1100 1100 1101 0000]

隐藏最高位后是[100 1100 1100 1100 1101 0000]

最终1.0-0.9二进制表示[1011 1101 1100 1100 1100 1100 1101 0000]

尾数小数点后对应的十进制是 (1 + 2^-1 + 2^-4 + 2^-5 + 2^-8 + 2^-9 + 2^-12 + 2^-13 + 2^-16 + 2^-17 + 2^-19) * 2^-4 = 0.100000024

所以通过上面的实例分析,我们了解了 为什么浮点数1.0-0.9 结果为 0.100000024

总结

二进制和浮点数是我们最初接触计算机时学习的基础理论,日常开发中也会经常遇到,但很容易忽视。下面总结下我学完本章内容收获到有意思的知识点:

  • 反码和补码诞生的原因:计算机中只有加法器,没有减法器。通过原码相加,结果会出错,所以出现了反码,又因为反码存在+0和-0问题,所以出现了补码。
  • 判断两个数符号是否相同:可以通过使用 ((b >> 31) ^ (a >> 31)) == 0 来判断两个整数正负是否相同
  • 浮点数分单精度和双精度:单精度浮点数占四个字节32位,从左到右,1位符号位,8位阶码位(指数的移码),23位尾数位
  • 浮点数加减运算:0值检测 -> 对阶 -> 尾数求和 -> 规格化 -> 结果舍入

更多文章

见我的博客:https://nc2era.com

written by AloofJr,转载请注明出处

目录
相关文章
|
6天前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
123 0
|
2月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
257 3
|
4月前
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
222 5
|
24天前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
273 4
|
2月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
218 2
|
2月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
2月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
14天前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
247 0
|
24天前
|
监控 Java API
2025 年全新出炉的 Java 学习路线:从入门起步到实操精通的详细指南
2025年Java学习路线与实操指南,涵盖Java 21核心特性、虚拟线程、Spring Boot 3、微服务、Spring Security、容器化部署等前沿技术,助你从入门到企业级开发进阶。
201 0
|
2月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
182 1