计算机编码--为什么整数中负数的除法和右移不是一回事

缘起

  最近在看卡耐基梅隆大学的【深入理解计算机系统实验】之datalab时,遇到一个题目:

 1 /*
 2  * divpwr2 - Compute x/(2^n), for 0 <= n <= 30
 3  *  Round toward zero
 4  *   Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
 5  *   Legal ops: ! ~ & ^ | + << >>
 6  *   Max ops: 15
 7  *   Rating: 2
 8  */
 9 int divpwr2(int x, int n) {
10
11 } 

即只能用题目提供的操作实现 x/(2^n) 的计算。对于正数,没什么可说的,直接x>>n即可。

但是负数也是这样吗,仔细一看,才发现  x/(2^n) 和 x>>n不是一回事。

比如 -33/(2^4) = -2, 但是另一方面,-33的编码为0xFFFFFFDF,右移4位变成0xFFFFFFFD,即-3,显然不一致。

思考中的犯傻

  对于上述问题,在做最后的解释前,先插入一段我的思考过程,主要是记录其中的犯傻之处。

由于计算机中整数的表示法为 2的补码(two‘s complemet representation),如下公式:

其中s为符号位,x为其他位,N为位数。

对于正数,s为0,只剩后面的求和项,除以(2^n)的话,从公式上看确实是直接右移n位即可。

接着这个思路,我就想,那么负数除以(2^n)等于:

其中第二项跟正数一样,右移n位即可,第一项是什么呢?c语言中的int类型位数太长,这里简单以8位代替。

-2^(8-1)   = -128(十进制) = 1000 0000(二进制)

-2^(8-1-1)= -64(十进制) =   1100 0000(二进制)

-2^(8-1-2)= -32(十进制) =   1110 0000(二进制)

也就是说,前面说的第一项其实也是原式的第一项右移n位。所以,总体来说负数除法也是右移。

但是,这个结论显然与-33/(2^4)那个例子矛盾,可是错误在哪儿呢?

其实,错误就在于把x掰开成两个,然后整除2^n,在把结果加起来,这个过程与x直接整除2^n是不等价的。

比如:6/2=3,但是 3/2+3/2=1+1=2。虽然错误很明显,但是一开始思考的时候却犯傻了。

原因应该是什么

负数除法与移位不同的原因用下面一张图就能说明白:

  如图所示,中间是一条数轴,数轴上面是x与x/(2^4)的对应关系,下面是x与x>>4的对应关系,

设  x/(2^4)=f(x)>>4,那么从上图可以看出,当x/(2^4)得到-1时,f(x)>>4为了得到-1,f(x)要比x向右移动15,或2^4-1。

图中具体是-16->-1或者-31->-16。

  因此,x / (2^n) = (x + (2^n-1)) >> n,所以,datalab那个题目的答案可以是:

1 int divpwr2(int x, int n) {
2     //all zeros or all ones
3     int signx=x>>31;
4     //int mask=(1<<n)+(-1);
5     int mask=(1<<n)+(~0);
6     int bias=signx&mask;
7     return (x+bias)>>n;
8 } 

参考文章:

(1)负数的除法和右移的区别

(2)datalab 深入理解计算机系统实验

原文地址:https://www.cnblogs.com/tlz888/p/9185403.html

时间: 2024-10-11 16:23:23

计算机编码--为什么整数中负数的除法和右移不是一回事的相关文章

二柱子问题扩充:1、题目避免重复; 2、可定制(数量/打印方式); 3、可以控制下列参数: 是否有乘除法、是否有括号、 数值范围、加减有无负数、除法有无余数、否支持分数 (真分数, 假分数, …)、是否支持小数 (精确到多少位)、打印中每行的间隔可调整;

程序设计思想 程序的主要设计思想为用字符串数组保存生成的运算题,将操作数采用单独算法处理,然后进行类型转换和操作符一起存入数组中,鉴于字符串的特性,可以对字符串的长度进行随意添加而不必考虑长度问题,最后进行字符串数组的输出产生客户要求的运算题; 源代码 #include<stdlib.h> #include<conio.h> #include<time.h> #include<iostream> #include<string> #include

求出整数中1的个数

输入一个整数,求出它的二进制1的个数.考虑的知识点:负数怎么求,因为计算机中存放都是补码的形式存储一个数.因为正数的源码,反码,补码都是一样,不用考虑.但是负数就要考虑了,比如-0,它的源码应该是10000000 00000000 00000000 000000000 00000000,所以负数要考虑. 下面是代码实现: #ifndef _FINDNUMBEROF1_ #define _FINDNUMBEROF1_ /*================================ Macro

C语言负数的除法和求余运算

假定我们让 a 除以 b,商为 q,余数为 r: q = a / b; r = a % b; 这里,不妨假定 b 大于 0. 我们希望 a.b.q.r 之间维持怎样的关系呢? 1.最重的一点,我们希望 q * b + r == a,因为这是定义余数的关系. 2.如果我们改变 a 的正负号,我们希望这会改变 q 的符号,但这不会改变 q  的绝对值. 3.当 b>0 时,我们希望保证 r >= 0 且 r < b.例如,如果余数用于哈希表的索引,确保 它是一个有效的索引值很重 . 这三条性

如何从40亿整数中找到不存在的一个

前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况下,如何解决该问题?如果有几个外部的"临时"文件可用,但是仅有几百字节的内存,又该如何解决该问题? 分析 这仍然是<编程珠玑>中的一个问题.前面我们曾经提到过<位图法>,我们使用位图法解决了这个问题.32位整型最多有4294967296个整数,而很显然40亿个数中必然会至少缺一个.我们同样也可以尝试使

整数中1 的个数

问题 求整数中1的个数 原理 一个数n若不为0,从数的最低为到最高位,第一个为1 的位记为 i ,则减去 1 之后,改为变为0,i 右边的所有位都变为1,因此 n & (n-1)可得 n中剩余比 i 更高位序 的 部分 ,循环此操作,则可得 1 的计数. 代码 1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 int n,s,counter = 0; 7 cout << " Plea

Java中负数以及类型转换问题

学习过java的都知道,在java中,不是直观的表示负数,而是采用补码的形式表示负数. 这是为了硬件操作的方便,把减法也转换成加法来运算. 那补码是怎样表示的呢?为了得到补码,我们引入了反码. 对于正数来讲,它的反码补码都为本身,如果不明白为什么,我们可以这样理解:引入反码补码的原因就是为了解决减法的问题,换句话数就是解决java中负数的问题,正数不存在这些问题,所以它的反码补码就是它本身.在有符号的基本数据类型中,最高位0表示正数,最高位1表示负数. 对于负数来讲,它的反码就是除去符号位取反,

自己动手写Java大整数《3》除法和十进制转换

之前已经完成了大整数的表示.绝对值的比较大小.取负值.加减法运算以及乘法运算.具体见前两篇博客(自己动手写Java * ). 这里添加除法运算. 另外看到作者Pauls Gedanken在blog(http://paul-ebermann.tumblr.com/post/6312290327/big-numbers-selfmade-part-2-14-conversion-from)中的转换十进制数到大整数的方法,这里一并列出. 除法 除法使用经典的除法法则,但是有几个需要注意的问题,下面列出

高效求一个整数中1的位数

求一个整数中0或1的位数,有很多方法可以使用除法,求余等方法,也可以使用位运算,相比前者效率更高. #include <stdio.h> #include <stdlib.h> //求一个整数 1的位数 int count0(int x) { int num=0; while(x) { num+=x%2; x/=2; } return num; } int count1(int x) { int num=0; while(x) { num+=(x&0x01); x>&

全国计算机等级考试科目中加入 二级Python

2018年9月份在全国计算机等级考试科目中加入"二级Python",但现在还没有出台完整详细的考试大纲,官方网站也仅仅是简单说明了考试时长(120分钟).大概形式(网络环境.无纸化)和环境要求(Python 3.5.2以上IDLE),考试大纲暂时还没有提供下载. 新增的二级Python考试科目内容肯定会以基本数据类型.运算符.内置函数.选择结构与循环结构.函数定义与调用.面向对象编程基础.简单的文件操作和数据库操作为主,重点考查基本功,并不会涉及太多标准库(比较常用的像math.ran