基础野:细说无符号整数
Brief
本来只打算理解JS中0.1 + 0.2 == 0.30000000000000004的原因,但发现自己对计算机的数字表示和运算十分陌生,于是只好恶补一下。
本篇我们一起来探讨一下基础的基础——无符号整数的表示方式和加减乘除运算。
Encode
无符号整数只能表示大于或等于零的整数值。其二进制编码方式十分直观,仅包含真值域。
我们以8bit的存储空间为例,真值域则占8bit,因此可表示的数值范围是{0,...,255},对应的二进制编码是{00000000,...,11111111}。
从集合论的角度描述,我们可以将十进制表示的数值范围定义为集合A,将二进制表示的数值范围定义为集合B,他们之间的映射为f。f(a)=b,其中a属于A、b属于B。并且f为双射函数。因此无符号整数表示方式具有如下特点:
1. 可表示的数值范围小;
2. 十进制表示的数值范围与二进制表示的数值范围的元素是一一对应的,两者可精确映射转换。(相对浮点数而言,某些二进制表示的数值只能映射为十进制表示的数值的近似值而已)
Zero-extend
零扩展运算用于在保持数值不变的前提下,不同字长的整数之间的转换。
例如现在我们要将8bit的00000100扩展为16bit,那么我们只要将高8bit设置为0即得到000000000000000100,而其数值并不产生变化。
Truncation
截断会减少位数,并对原始值取模。模为2^n,n为截断后的位数。
例如现在将16bit的000000100000000100截断为8bit,那么结果为00000100,而模是2^8。
Addition
注意:位级运算均是模数运算,即加减乘除后均会对运算结果取模,并以取模后的结果作为终止返回。
无符号整数加法的运算顺序:
1. 算术加法;
2. 执行截断操作。
示例,两个4bit的无符号数相加(11+6):
1011
+0110
10001,然后执行截断得到0001
Subtraction
无符号整数减法的运算顺序:
1. 将减法转换为加法(对减数取补码);
2. 算术加法;
3. 执行截断操作。
示例,两个4bit的无符号数相减(11-6):
1011
-0110
对减数求补码后,减法转换为加法
1011
+1010
10101,然后执行截断得到0101
Multiplication
对于乘法实质上就是通过移位操作和加、减法组合而成,且根据乘数是否为2的n次幂区别处理。
1. 对于乘数为2的n次幂的情况,乘法公式为:a<<n,如6*4等价于6*(2^2),则可转换为移位操作6<<2即可。然后再对结果取模。
2. 对于乘数不为2的n次幂的情况
2.1. 将乘数以二进制形式表示,并以连续的1作为分组。如43的二进制形式为00(1)0(1)0(11),从左至右可分成3组分别是(1)、(1)和(11)。
2.2. 以n表示每组的最高位的指数,以m表示每组最低位的指数。如第一组n=m=5,第二组n=m=3,第三组n=1而m=0。
2.3. 根据公式(x<<n+1)-(x<<m)对每组进行运算,并将结果相加。如(假设被乘数为2)
第一组:2<<(5+1) - 2<<5 = 64
第二组:2<<(3+1) - 2<<3 = 16
第三组:2<<(1+1) - 2<<0 = 6
相加得到86
2.4. 对结果取模。
Dividision
对于除法实质上就是通过移位操作和加、减法组合而成,且根据除数是否为2的n次幂区别处理。
1. 对于被除数为2的n次幂的情况,除法公式为:a>>n,如6/4等价于6/(2^2),则可转换为移位操作6>>2即可。然后再对结果取模。
2. 对于被除数不为2的n次幂的情况,则情况复杂不少。运算步骤如下:(实质上我们就是按这个步骤做十进制除法的)
2.1. 高位对齐,在除数值小于被除数值的前提下,让除数的位数等于被除数;若执行高位对齐后,除数值大于被除数时,则除数右移一位。得到位移数。
2.2. 试商,除数-被除数*N = 余数中间值 ,其中N*被除数 <= 除数 && (N+1)*被除数 > 除数。商 = 商 + N * 基数^位移数。
2.3. 循环执行上述步骤,直到无需再执行高位对齐,那么2.2中得到的余数中间值将作为除法运算的最终余数,否则余数中间值则作为一下轮高位对齐的被除数处理。
以下是C的实现:
#include <stdio.h>
// 前置条件
const unsigned short lowest_bit_weight = 1; // 二进制最低位的位权重
int main(){
// 输入
unsigned short dividend = 14, divisor = 5;
// 输出
unsigned short quotients = 0, // 商
rem = 0; // 余数
// 中间值
unsigned short highest_bit_weight,
divisor_aligned,
tmp_dividend = dividend;
unsigned short high_alignment;
// 开始运算
while (1){
// 高位对齐 (从高位开始运算)
// 结果:1. 要么被除数的最高位小于除数的最高位;
// 2. 要么被除数的最高位对齐除数的最高位, 且被除数大于除数;
high_alignment = 0;
highest_bit_weight = lowest_bit_weight;
divisor_aligned = divisor;
while (tmp_dividend >= divisor_aligned){
divisor_aligned = divisor_aligned << 1;
highest_bit_weight = highest_bit_weight << 1;
high_alignment += 1;
}
if (high_alignment > 0){
divisor_aligned = divisor_aligned >> 1;
highest_bit_weight = highest_bit_weight >> 1;
high_alignment -= 1;
}
// 当无需执行高位对齐时,则将下一轮的被除数作为余数,并且结束运算
if (0 == high_alignment) {
rem = tmp_dividend;
break;
}
// 上一轮运算的商加上最高位权重得到当前运算的商值
quotients = quotients | highest_bit_weight;
// 被除数减除数的差值作下一轮的被除数
tmp_dividend = tmp_dividend - divisor_aligned;
}
printf("%u/%u=%u(rem:%u)n", dividend, divisor, quotients, rem);
return 0;
}
Conclusion
尊重原创,转载请注明
Thanks
《深入理解计算机系统》
- 洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is He…【字符串+模拟】
- 洛谷 P1055 ISBN号码【字符串+模拟】
- 【Java学习笔记之十二】Java8增强的工具类:Arrays的用法整理总结
- 利用insert,update和delete注入获取数据
- 【机器学习笔记之二】决策树的python实现
- 【Java学习笔记之十三】初探Java面向对象的过程及代码实现
- 洛谷 P1308 统计单词数【字符串+模拟】
- 【Java学习笔记之十四】Java中this用法小节
- Codeforces 839E Mother of Dragons【__builtin_popcount()的使用】
- 【Java学习笔记之十五】Java中的static关键字解析
- Codeforces 839D Winter is here【数学:容斥原理】
- Codeforces 839C Journey【DFS】
- Facebook的漏洞可以让攻击者在分分钟内重置用户账户密码
- 【Java学习笔记之十七】Java中普通代码块,构造代码块,静态代码块区别及代码示例分析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP学习记录之面向对象(Object-oriented programming,OOP)基础【接口、抽象类、静态方法等】
- laravel 解决crontab不执行的问题
- PHP+Redis开发的书签案例实战详解
- Python持续监听文件变化代码实例
- laravel框架如何设置公共头和公共尾
- PHP结合Redis+MySQL实现冷热数据交换应用案例详解
- 浅谈Laravel模板实体转义带来的坑
- Vagrant(WSL)+PHPStorm+Xdebu 断点调试环境搭建
- PHP大文件切割上传功能实例分析
- laravel Task Scheduling(任务调度)在windows下的使用详解
- PHP 7.4中使用预加载的方法详解
- PHP设计模式之工厂模式(Factory)入门与应用详解
- Laravel 实现Controller向blade前台模板赋值的四种方式小结
- Referer原理与图片防盗链实现方法详解
- Laravel 简单实现Ajax滚动加载示例