【从入门到放弃-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,转载请注明出处

目录
相关文章
|
17天前
|
开发框架 IDE Java
java制作游戏,如何使用libgdx,入门级别教学
本文是一篇入门级教程,介绍了如何使用libgdx游戏开发框架创建一个简单的游戏项目,包括访问libgdx官网、设置项目、下载项目生成工具,并在IDE中运行生成的项目。
34 1
java制作游戏,如何使用libgdx,入门级别教学
|
8天前
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
13 2
|
1月前
|
Java 程序员 UED
Java中的异常处理:从入门到精通
【9月更文挑战第23天】在Java编程的世界中,异常是程序执行过程中不可避免的事件,它们可能会中断正常的流程并导致程序崩溃。本文将通过浅显易懂的方式,引导你理解Java异常处理的基本概念和高级技巧,帮助你编写更健壮、更可靠的代码。我们将一起探索如何捕获和处理异常,以及如何使用自定义异常来增强程序的逻辑和用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供有价值的见解和实用的技巧。
35 4
|
2月前
|
设计模式 前端开发 Java
【前端学java】SpringBootWeb极速入门-分层解耦(03)
【8月更文挑战第13天】SpringBootWeb极速入门-分层解耦(03)
19 2
【前端学java】SpringBootWeb极速入门-分层解耦(03)
|
2月前
|
开发框架 前端开发 Java
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
【8月更文挑战第12天】SpringBootWeb极速入门-实现一个简单的web页面01
59 3
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
|
2月前
|
JSON 前端开发 Java
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
【8月更文挑战第12天】SpringBootWeb极速入门-请求参数解析(02)
19 1
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
|
2月前
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
2月前
|
存储 Java 程序员
Java中的集合框架:从入门到精通
【8月更文挑战第30天】在Java的世界里,集合框架是一块基石,它不仅承载着数据的存储和操作,还体现了面向对象编程的精髓。本篇文章将带你遨游Java集合框架的海洋,从基础概念到高级应用,一步步揭示它的奥秘。你将学会如何选择合适的集合类型,掌握集合的遍历技巧,以及理解集合框架背后的设计哲学。让我们一起探索这个强大工具,解锁数据结构的新视角。
|
2月前
|
Java 程序员 UED
Java中的异常处理:从入门到精通
【8月更文挑战第28天】在Java编程的世界里,异常处理是一块基石,它确保了程序的健壮性和可靠性。本文将通过深入浅出的方式,带你了解Java异常处理的基本概念、分类、以及如何有效地捕获和处理异常。我们将一起探索try-catch-finally结构的奥秘,并学习如何使用throws关键字声明方法可能会抛出的异常。此外,我们还会讨论自定义异常类的创建和使用,以及最佳实践。无论你是Java新手还是有一定经验的开发者,这篇文章都将为你提供宝贵的知识,帮助你编写出更加稳定和可靠的代码。
|
2月前
|
编解码 网络协议 Oracle
java网络编程入门以及项目实战
这篇文章是Java网络编程的入门教程,涵盖了网络编程的基础知识、IP地址、端口、通讯协议(TCP和UDP)的概念与区别,并提供了基于TCP和UDP的网络编程实例,包括远程聊天和文件传输程序的代码实现。
java网络编程入门以及项目实战