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

目录
相关文章
|
1月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
50 3
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
18天前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
24天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
65 5
|
21天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
32 1
|
28天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
44 3
|
29天前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
1月前
|
开发框架 IDE Java
java制作游戏,如何使用libgdx,入门级别教学
本文是一篇入门级教程,介绍了如何使用libgdx游戏开发框架创建一个简单的游戏项目,包括访问libgdx官网、设置项目、下载项目生成工具,并在IDE中运行生成的项目。
51 1
java制作游戏,如何使用libgdx,入门级别教学
|
1月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
45 4
|
30天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
23 1
下一篇
无影云桌面