从JavaScript的移位运算看数字在计算机内部的编码——补码
偶然看到一个JavaScript的题目:
js中13>>2=? -13>>2=?
在浏览器中很容易测试出答案分别是 3 和 -4。
13>>2 = 3 很好理解,但是对于 -13>>2 = -4 ,我无法理解。然后我又去用 Java 语言实现一遍,结果也是一样的。
我知道关于 “原码、反码、补码” 这个知识点是 《计算机组成原理》 中的内容,但苦于网上下载不到该书,只得去Google各种博客去深入了解。
(吐槽下,Google搜索“原码 反码 补码”的结果,比度娘搜索该关键词的结果好太多!用百度真真是浪费生命。)
看着充满数学公式,和无聊的 “计算方法”,都是些教材上的理论东西,越看越迷糊。后来,我就总结了:
原码、反码、补码
其实,抛开应付《计算机组成原理》考试,以后(无论笔试、面试、Coding)遇到的都是 “补码”,没人关心 原码 和 反码 是个什么。
为什么呢? 因为 在计算机内部(寄存器层面),数都是以补码的形式存储在寄存器中的。
原码,反码什么的都是为了计算补码存在于人类思维中的,可无视它们。
比如,此题中,13 是原始数,移位移的是 13 的补码(下面细讲JavaScript整数的补码),移位后再将 补码 反算成 原始数。
补码的简便计算方法
无论C/C++/Java强类型语言,还是JavaScript,都有移位运算符,在CPU中的运算器进行移位操作时,这个过程都是一样的。
这里为了方便,假设 在一个寄存器只有8位的机器上,整型只能用 8个比特位来表示。本文讲的都是有符号数。
那么,
13的 补码 就可以表示为:(其中D 表示 十进制数, B 表示二进制数)
13 (D) = 0000 1101 (B)
-13 的 补码 就是:
-13 (D) = 1101 0011 (B)
那么 -13 的补码是怎么算出来的呢? 很简单,只要 让 -13 的补码 加上 13 的补码, 溢出后变成 0 就行了。
这有什么根据么? 当然有根据了,我们知道为什么发明 补码么? 就是因为 原码 和 反码 会出现 +0 和 -0 ,
所以才不为计算机科学家采用。而接着发明出来的补码,恰好补上这一漏洞,使得计算机可以和正常的数学运算
一样,完美的完成加减乘除。
所以,如果 在数学里,
13 + (-13) = 0
那么,在计算机的世界里,必须
0000 1101 + 1101 0011 = 0
因为造出补码的意义就是为了让计算机内补码和数学中的数字一一对应,就是能完全代表数学中的数。这样才能使用传承了
几千年的数学知识进行更高深的微积分等科学计算。
这里举一个简单的例子。
一台8位寄存器的机器,13 的补码 是 0000 1101 ,那 -13 的补码呢?
根据 -13 + 13 = 0 ,可以这么算:
0000 1101
+ xxxx xxxx
———————
1 0000 0000 (8寄存器,进位的1溢出被丢弃。寄存器中就保存的是 0000 0000 ,就是数学中的0)
这样,很容易凑出 -13 的补码,再简单不过了。就只有1和0进行加减。-13的补码可以算出 是 1111 0011。
比之,教科书式的
  
(这么头大的公式,你会去用,这明显是数学家们为了论述严密而显摆给学生们看的)
和一般教学中用的:
负数的补码,是反码加1。而反码又是原码,符号位不变,加1。
(差不多,只是多了个抽象概念。反码没有必要记忆,时代遗留产物,大胆抛弃)
>> << 移位运算
算出了 13 和 -13 的补码,接下来就是 右移 2 位了。
无论什么语言,只要是在X86的机器,其移位运算在汇编层都是通过 算术右移指令SAR(shift arithmetic right)执行的。而算术右移指令的
具体操作是,将寄存器中的数值(就是1和0的数串)右移,最左端用最高位填充,而不是补零。这是X86汇编规定的,至于为什么有些原因,不赘述。
那么, 13 的补码右移 2 位后是:
13的补码: 13 (D) = 0000 11 (B)
13的补码右移2位后: 0000 0011 (最右边的01被丢掉)
-13的补码: -13 (D) = 1111 00 (B)
-13的补码右移2位后: 1111 1100 (最右边的11被丢掉)
移位后反算出数值
移位完了,并不代表结束了。移位后的数字还在寄存器中,是未知数(我们要求的未知数)的补码的数串。
然后,我们要根据补码,算出该数来。
依据《计算机组成原理》:正数是原码、反码、补码三码合一。
13 的补码右移2位后的数串(未知数的补码形式),换算成十进制,就是 3。
13 移位后的补码容易换成十进制(因为是正数,所以很容易算出来),而 -13 的移位后的补码究竟是十进制的多少,就要小算一下。
用上面的方法你能算出来吗?
计算过程:
-13的补码右移2位后: 1111 1100
+ xxxx xxxx
———————
1 0000 0000
凑出 一个加数来,应该很简单吧。第二个加数xxxx xxxx就是 0000 0100 ,就是 十进制的 4。
就是说 1111 1100 是 -4 , -13移位后的补码是 十进制的 -4 。
答案就出来了。 13 >> 2 等于 3, -13 >> 等于 -4.
再说JavaScript
前面说的是 8位 的机器,数字都是用8个比特位来表示。再出现C/C++/Java等高级语言后,数据类型的宽度由语言本身决定,如C/C++的int/long由具体实现语言标准的编译器决定每个数据类型到底需要多少个比特位,而Java则因为有JVM的存在,整型统一为32位。
那在JavaScript中,这个整数到底是用多少个比特位表示呢?。JavaScritp 是这样的:
根据这个问答中有人讲:
在
Javascript权威指南 第六版 3.1小节有讲到:Number类型统一按浮点数处理,64位存储,整数是按最大54位来算最大最小数的,否则会丧失精度;某些操作(如数组索引还有位操作)是按32位处理的~~
浮点数范围as large as±1.7976931348623157×10的308次方
as small as±5×10的−324次方精确整数范围:
TheJavaScript number format allows you to exactly represent all integers between
−9007199254740992and9007199254740992(即正负2的53次方)数组索引还有位操作:
正负2的31次方
鉴于大家可能没书,下载PDF也费事,就贴出图片来。


根据图片,32位整数,就是说题中的 13 在JavaScript 中是如下表示的:
13 = 0000 0000 0000 0000 0000 0000 0000 1101 (13的补码)
字节编号 1 2 3 4 5 6 7 8
-13 = 1111 1111 1111 1111 1111 1111 1111 0011 (-13的补码)
当然,也有另外一种简单计算补码的方法:
负数的补码这么记简单。
符号位不变。其他的从低位开始,指导遇见第一个1之前,什么都不变。遇见第一个1后保留这个1,以后按位取反。
例:[-7]原= 1 0000111 B
[-7]补= 1 1111001 B
技巧性很强,千万别记错了。
反正我的方法就是:记住正数和负数的补码加起来也是0就对了。(而正数是三码合一)
另附一个有趣小问题:
你知道下面的C代码会出现什么问题吗?(注意移位操作)
#include <stdlib.h>
#include <stdio.h> static void divide_by_two(int num)
{
while (num) {
printf("%d\n", num);
num = num>>;
}
} int main()
{
int num;
scanf("%d", &num); divide_by_two(num); return ;
}
答案见:http://blog.chinaunix.net/uid-23629988-id-3018793.html
PS:在JS中,位运算基本没用。这是强类型语言C/C++/Java才经常用到的内容。
.
从JavaScript的移位运算看数字在计算机内部的编码——补码的更多相关文章
- Java中的位运算符、移位运算
		一.位运算 Java中有4个位运算,它们的运算规则如下: (1)按位与 (&) :两位全为1,结果为1,否则为0: (2)按位或 (|) :两位有一个为1,结果为1,否则为0: (3) ... 
- 【原创】Java移位运算
		学习移位运算,首先得知道参与移位运算的类型的位数,那先来复习下Java基础类型的占位数吧. Java基础类型 Java基础类型总结一览表 类型 二进制位数 最大值 最小值 初始化值 表示形式 带符号 ... 
- 【JavaScript】深入分析JavaScript的关系运算和if语句
		JavaScript的关系运算,没有我原想的那么简单.等终于理清它的运算逻辑之后,我的头大了至少一圈.而if语句的真假判定逻辑本身不难,但要把它和关系运算联系起来,相信你会和我一样,到达崩溃边缘.不信 ... 
- << 移位运算
		/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constr ... 
- 浅谈JavaScript浮点数及其运算
		原文:浅谈JavaScript浮点数及其运算 JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ... 
- java  移位运算
		移位运算 :将整数转化为二进制(以补码的形式),按位平移. << 左移 >> 右移 >>> 无符号右移 << 右移: 按位做平 ... 
- JavaScript 浮点数及运算精度调整总结
		JavaScript 浮点数及运算精度调整总结 JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题不是J ... 
- java中位运算和移位运算详解
		一.位运算 (1)按 位 与 & 如果两个相应的二进制形式的对应的位数都为1,则结果为1,记为同1为1,否则为0.首先我们看一下对正数的运算 分别看一下正数和负数的具体运算步骤 ... 
- BUG 记录:移位运算与扩展欧几里得算法
		BUG 记录:移位运算与扩展欧几里得算法 起因 上个月就开始打算用C++写一个ECC的轮子(为什么?折磨自己呗!),奈何自己水平有点差,拖到现在才算写完底层的大数运算.在实现欧几里得算法的时候,我开始 ... 
随机推荐
- 9.30 noip模拟试题
			时限均为1s,内存 256MB 1.某种密码(password.*) 关于某种密码有如下描述:某种密码的原文A是由N个数字组成,而密文B是一个长度为N的01数串,原文和密文的关联在于一个钥匙码KEY. ... 
- 给Sublime Text2安装轻量级代码提示插件:SublimeCodeIntel
			步骤: 1.下载SublimeCodeIntel(地址https://github.com/SublimeCodeIntel/SublimeCodeIntel): 2.将下载的压缩包解压,并放置在Pa ... 
- android  listview 替代品recyclerview详解
			安卓v7支持包下的ListView替代品————RecyclerView RecyclerView这个控件也出来很久了,相信大家也学习的差不多了,如果还没学习的,或许我可以带领大家体验一把这个艺术 ... 
- strut2.xml中result param详细设置
			1.Struts2.xml配置文件: 2.Jsp中:说明回调函数一个参数即可.把上面的俩个参数msg和page封装到一起了 3.msg是Action中全局变量 可参考:http://qiaolevip ... 
- oracle备份表
			oracle与sql单表备份的区别 ( oracle中备份表: create table 备份表名 as select * from 原表 sql server中备份表: select * i ... 
- java_设计模式_适配器模式_Adapter Pattern(2016-08-09)
			概念 将一个接口转换成客户希望的另外一个接口.(该模式使得原本不兼容的类可以一起工作). UML图 适配器模式有类的适配器模式和对象的适配器模式两种不同的形式. (1)对象的适配器模式结构图 (2)类 ... 
- 【BZOJ2741】【块状链表+可持久化trie】FOTILE模拟赛L
			Description FOTILE得到了一个长为N的序列A,为了拯救地球,他希望知道某些区间内的最大的连续XOR和. 即对于一个询问,你需要求出max(Ai xor Ai+1 xor Ai+2 .. ... 
- fish code
			<embed width="272" height="180" type="application/x-shockwave-flash" ... 
- JQuery 绑定回车事件 兼容ie8,ie9
			$("#form-search").find('#search-query').bind('keypress', function(e) { var keycode; if(win ... 
- PHP时间戳和日期相互转换
			在php中我们要把时间戳转换日期可以直接使用date函数来实现,如果要把日期转换成时间戳可以使用strtotime()函数实现,下面我来给大家举例说明. 1.php中时间转换函数 strtotime ... 
