从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的轮子(为什么?折磨自己呗!),奈何自己水平有点差,拖到现在才算写完底层的大数运算.在实现欧几里得算法的时候,我开始 ...
随机推荐
- CakePHP之Model
模型 模型在应用程序中是作为业务层而存在的(怎么感觉是数据层......).这就意味着,模型应当负责管理几乎所有涉及数据的事情,其合法性,以及你的业务领域中数据在工作流程中的演化和互动 . 通常模型类 ...
- 我的博客css得到别人的认可
你好 在吗?? 本消息来自QQ临时会话,如果您收到骚扰信息,可点击以下网址中的"停用服务"关闭临时会话: http://shang.qq.com/widget/set.php 20 ...
- awr相关指标解析
awr相关指标解析 2016年11月11日 15:09
- 【转】iOS-Core-Animation-Advanced-Techniques(四)
原文:http://www.cocoachina.com/ios/20150105/10812.html 隐式动画和显式动画 隐式动画 按照我的意思去做,而不是我说的. -- 埃德娜,辛普森 我们在第 ...
- Java study 2:The note of studying Socket which based TCP
TCP concept: 传输控制协议(Transmission Control Protocol, TCP)是一种面向连接(连接导向)的.可靠的.基于字节流的运输层(Transport layer) ...
- java问题整理
1.一个“.java”源文件中是否可以包括多个类(不是内部类)?有什么限制? 答:可以有多个类.但只能有一个public类.并且public类名必须与文件名相一致. 2.Java有没有goto? ...
- sqlserver-事务处理
事务的概念:简单说就访问并可能更新数据库中各种数据项的一个程序执行单元,一旦开启事务,所有对数据的操作要么全部执行,要么全部都不执行.单条sql语句本身就是一个事务. 事务的属性: 事务是作为单个逻辑 ...
- C++序列化库的实现
C++中经常需要用到序列化与反序列化功能,由于C++标准中没有提供此功能,于是就出现了各式各样的序列化库,如boost中的,如谷歌的开源项目,但是很多库都依赖其他库过于严重,导致库变得很庞大.今天来分 ...
- phpcms 如何获取文章
请求地址http://127.0.0.1/phpcms/index.php?m=content&c=index&a=show&catid=6&id=8 先来判断地址对应 ...
- .net 实例化对象
定义TestClass,在调用的地方 TestClass a; 如果下面有引用 a.Property,VS会报错,而如果 TestClass a = null; 再次调用的话则不会报错. TestCl ...