前言

本篇博客以“SSD6-Exercise2-Data Lab: Manipulating Bits”为例,分析在对C语言中的整数采用补码(two’s-complement)编码的机器上,其整数运算的特性。

补码

定义

最常见的有符号数的计算机表示方式就是补码(two’s-complement)形式。在这个定义中,将字的最高有效位解释为负权(negative weight),我们用函数B2T(Binary to Two’s-complement的缩写,长度为 w)来表示。

如:

B2T ([0001]) = -0 + 0 + 0 + 1 = 1  B2T ([0101]) = -0 + 4 + 0 + 1 = 5  B2T ([1011]) = -8 + 0 + 2 + 1 = -5  B2T ([1111]) = -8 + 4 + 2 + 1 = -1

定理1

B2T ([11···1]) = -1

证明:假设B2T ([11···1]) 共有w位,则其值为 -2^(w-1) + 2^(w-2) + ··· + 2^0. 根据等比数列求和公式,易证该值为-1.

定理2

对于w位的补码B2T来说,其边界值Tmax与Tmin分别为:

Tmax = B2T ([01···1]) = 2^(w-1) - 1

Tmin = B2T ([10···0]) = -2^(w-1)

即有:~Tmax = Tmin

整数运算

我们先以表格的形式,宏观介绍C语言中的位级运算、逻辑运算和移位运算。

运算种类运算符主要说明位级运算|, &, ~, ^对应于布尔运算中的OR, AND, NOT, EXCLUSIVE-OR逻辑运算||, &&, !对应于命题逻辑中的OR, AND, NOT移位运算<>分为左移与右移,右移运算包括逻辑右移与算数右移

!与~有什么区别?

注意:逻辑运算很容易和位级运算相混淆,但是它们的功能是完全不同的。

逻辑运算中认为所有非零的参数都表示TRUE,而参数0表示FALSE.逻辑运算的结果为一个布尔值,而位级运算的结果依然为一个数.逻辑运算的运算符常称为与、或、非,而位级运算的运算符常称为与、或、取反、异或.

因此,!是逻辑运算中的非运算符,而~是位级运算中的取反运算符。

逻辑右移与算术右移

我们先来看左移运算<<.>

对操作数x执行x<

相应而言的右移运算>>.

对操作数x执行x>>k运算,即x向右移动k位。此运算会丢弃最低的k位,那么在左端需要补充的k个位是什么呢?

若执行逻辑右移,则补充k个0,这类似于左移运算.

若执行算术右移,则补充k个最高有效位的值。

且几乎所有的编译器/机器组合都对有符号数使用算术右移,对无符号数采用逻辑右移。

运算特性

我们通过完成这下面这10个函数,来体会补码的整数运算特性。

/*

* bitAnd - x&y using only ~ and |

*   Example: bitAnd(6, 5) = 4

*   Legal ops: ~ |

*   Max ops: 8

*   Rating: 1

*/

int bitAnd(int x, int y) {

return ;

}

/*

* bitOr - x|y using only ~ and &

*   Example: bitOr(6, 5) = 7

*   Legal ops: ~ &

*   Max ops: 8

*   Rating: 1

*/

int bitOr(int x, int y) {

return ;

}

/*

* isZero - returns 1 if x == 0, and 0 otherwise

*   Examples: isZero(5) = 0, isZero(0) = 1

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 2

*   Rating: 1

*/

int isZero(int x) {

return ;

}

/*

* minusOne - return a value of -1

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 2

*   Rating: 1

*/

int minusOne(void) {

return ;

}

/*

* TMax - return maximum two's complement integer

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 4

*   Rating: 1

*/

int tmax(void) {

return ;

}

/*

* bitXor - x^y using only ~ and &

*   Example: bitXor(4, 5) = 1

*   Legal ops: ~ &

*   Max ops: 14

*   Rating: 2

*/

int bitXor(int x, int y) {

return ;

}

/*

* getByte - Extract byte n from word x

*   Bytes numbered from 0 (LSB) to 3 (MSB)

*   Examples: getByte(0x12345678,1) = 0x56

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 6

*   Rating: 2

*/

int getByte(int x, int n) {

return ;

}

/*

* isEqual - return 1 if x == y, and 0 otherwise

*   Examples: isEqual(5,5) = 1, isEqual(4,5) = 0

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 5

*   Rating: 2

*/

int isEqual(int x, int y) {

return );

}

/*

* negate - return -x

*   Example: negate(1) = -1.

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 5

*   Rating: 2

*/

int negate(int x) {

return ;

}

/*

* isPositive - return 1 if x > 0, return 0 otherwise

*   Example: isPositive(-1) = 0.

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 8

*   Rating: 3

*/

int isPositive(int x) {

return ;

}

下面则分模块讨论:每个函数所代表的补码的整数运算特性。

逻辑运算与位级运算

这两个函数要分别实现位级运算中的与和或操作。

由于二进制表示的数位只有0与1,所以我们在思考位级运算的时候,可以借助逻辑运算/命题逻辑中的一些重要定律,即:把位级运算中的0想象成逻辑运算中的FALSE,把1想象成TRUE.

在命题逻辑中,有重要的德摩根律:

对命题p、q,有:

(1)p ∧ q ↔ ﹁(﹁p ∨ ﹁q)  (2)p ∨ q ↔ ﹁(﹁p ∧ ﹁q)

相应地,可以很快地推导出位级运算中的与和或操作。

/*

* bitAnd - x&y using only ~ and |

*   Example: bitAnd(6, 5) = 4

*   Legal ops: ~ |

*   Max ops: 8

*   Rating: 1

*/

int bitAnd(int x, int y) {

return ~(~x | ~y);

}

/*

* bitOr - x|y using only ~ and &

*   Example: bitOr(6, 5) = 7

*   Legal ops: ~ &

*   Max ops: 8

*   Rating: 1

*/

int bitOr(int x, int y) {

return ~(~x & ~y);

}

那么对于异或操作,命题逻辑中又是怎么定义的呢?

对命题p、q,有:  p ⊕ q ↔ (﹁p ∧ q) ∨ (p ∧ ﹁q)

故相应的:

/*

* bitXor - x^y using only ~ and &

*   Example: bitXor(4, 5) = 1

*   Legal ops: ~ &

*   Max ops: 14

*   Rating: 2

*/

int bitXor(int x, int y) {

return (x & ~y) | (~x & y);

}

异或的用途

从上面关于异或的定义中我们也可以看到:

p ⊕ q ↔ (﹁p ∧ q) ∨ (p ∧ ﹁q)

即:只有当p和q取值不同时,p ⊕ q 才为1(TRUE).

那么同样地,在位级运算中,我们可以通过异或的这一性质,用来判断两个数值是否相等。

/*

* isZero - returns 1 if x == 0, and 0 otherwise

*   Examples: isZero(5) = 0, isZero(0) = 1

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 2

*   Rating: 1

*/

int isZero(int x) {

return !(x^0);

}

/*

* isEqual - return 1 if x == y, and 0 otherwise

*   Examples: isEqual(5,5) = 1, isEqual(4,5) = 0

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 5

*   Rating: 2

*/

int isEqual(int x, int y) {

return !(x^y);

}

需要注意的是:这里的!不能改为~,因为这个函数所做的是一个逻辑运算:判断某个数是不是 0(或x与y是不是相等).(从函数的名字isZero、isEqual就可以看的出来:最外层进行的必须是一个逻辑运算)

特别的数:-1

在最上方的时候我们已经提过了补码中的一个重要定理:

B2T ([11···1]) = -1

那么如何取到[11···1]呢,很简单,因为数0可以表示为[00···0],所以对0进行按位取反操作即可。

/*

* minusOne - return a value of -1

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 2

*   Rating: 1

*/

int minusOne(void) {

return ~0;

}

特别的数:0

我们来看isPositive函数:判断一个数是否为正,对正数,返回值为1;对非正数,返回值为0.

根据补码的定义,我们很容易知道:最高有效位为1的数是负数。

那么最高有效位是0的数是正数吗?

不然,因为对于0来说,它的每一位都是0.

“数x最高有效位是否为1”很好判断:让x的最高有效位先跑到最右边,也就是x>>31,然后在与1按位取或,若最终结果为1,说明最高有效位就是1.

所以isPositive函数需要满足两个命题:

(1)x的最高有效位是0,即((x>>31) & 1) == 0  (2)x不是0,即x != 0

即:函数的返回值为:(((x>>31) & 1) == 0) && x(记为式*),由于具有运算符号的限制,我们还要对它继续进行转化。

对于((x>>31) & 1) == 0来说,由于(x>>31) & 1的结果只有1位,所以这个逻辑运算可以表达成:!((x>>31) & 1).

那么式*就变为了:!((x>>31) & 1) && x,由于命题!((x>>31) & 1)的值是0或1,命题x的值也是0或1,所以逻辑运算符&&可退化为:两个只有一个数位的数值的按位取与运算。

/*

* isPositive - return 1 if x > 0, return 0 otherwise

*   Example: isPositive(-1) = 0.

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 8

*   Rating: 3

*/

int isPositive(int x) {

return (!((x>>31) & 1)) & x;

}

边界值:Tmax与Tmin

由补码的定义我们可以知道:

对于w位的补码B2T来说,其边界值Tmax与Tmin分别为:

Tmax = B2T ([01···1]) = 2^(w-1) - 1

Tmin = B2T ([10···0]) = -2^(w-1)

即有:~Tmax = Tmin

那么我们需要得到Tmin,即[10···0]呢?

只需要[10···0] = 1<<31即可,再对其按位取反,便得到了Tmax.

/*

* TMax - return maximum two's complement integer

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 4

*   Rating: 1

*/

int tmax(void) {

return ~(1<<31);

}

移位运算的潜在含义

下面我们来看getByte函数:

/*

* getByte - Extract byte n from word x

*   Bytes numbered from 0 (LSB) to 3 (MSB)

*   Examples: getByte(0x12345678,1) = 0x56

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 6

*   Rating: 2

*/

int getByte(int x, int n) {

return ;

}

其中,LSB(Least Significant Bit)是“最低有效位”,MSB(Most Significant Bit)是“最高有效位”。

举个例子:对于十六进制数0x12345678,其二进制表示为[0001 0010 ··· 1000],其最低有效位是排列在最右那个0,而最高有效位是排列在最左边的那个0.

因此,这个函数想要表达的意思就是说:n的值从0到3,且0代表最低有效位(可以理解为排在最右边的那个字节,也就是0x78),3代表最高有效位(可以理解为排在最左边的那个字节,也就是0x12),同理:1代表的就是0x56.

那么我们该如何实现这个函数的功能呢?

我们可以把这个问题分三个步骤考虑:

通过n的值,我们就得到了其所代表的两个数位(比如:当n为1时,我们就得到,这两个数位是5 6;当n为2时,这两个数位就是3 4)我们又知道,最终得到的这两个数位,其实是在“最右边”的。依然拿n为1来举例子,我们在第一步得到了5 6,但我们得把这两个数放在最右边啊,否则不就成了0x5600吗,它一点都不等于0x56,即0x0056.第三步,我们把这两个数位放到最右边以后,还得保证它的左侧全部是0。这要怎么做呢——只需要让它和0x000000ff进行按位与操作即可。

这样转化了问题之后,我们的难点只剩下一个了,也就是上述过程中的第二步:我们要把这两个数位向右移动几位呢?

由于 1 byte = 8 bits,所以这个问题也很简单了:当n=1时,向右8位;当n=2时,向右16位…也就是说,我们只需要向右移动8n位就好了。

那么这个函数就很好写了:

/*

* getByte - Extract byte n from word x

*   Bytes numbered from 0 (LSB) to 3 (MSB)

*   Examples: getByte(0x12345678,1) = 0x56

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 6

*   Rating: 2

*/

int getByte(int x, int n) {

int offset = 8 * n;

int a = x >> offset;

int b = 0x000000ff;

return a & b;

}

细心的读者可能发现了,我们这个函数的实现是不符合题目要求的。题目中还有一个额外的要求,即:我们能够使用的运算符,只有! ~ & ^ | + << >>,这其中没有乘号*.

那么,8 * n又该怎么表示呢?

这个问题也很简单。我们都喜欢拿十进制来思考问题,就比如说:100 * x是多少呢?小学生都知道:在x的右边添上两个0啊!那1000 * x呢?添3个0啊!

好了,那么回到我们的二进制。2 * x是多少呢?大学生应该可以知道了:在x的右边添1个0啊!那8 * x呢?添3个0啊!

这也就是移位运算的潜在含义了,我们把x向左移k位,其实就是在说:把x * 2^k.

经过修正后的函数如下:

/*

* getByte - Extract byte n from word x

*   Bytes numbered from 0 (LSB) to 3 (MSB)

*   Examples: getByte(0x12345678,1) = 0x56

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 6

*   Rating: 2

*/

int getByte(int x, int n) {

int offset = n << 3;

int a = x >> offset;

int b = 0x000000ff;

return a & b;

}

补码中的“相反数”

/*

* negate - return -x

*   Example: negate(1) = -1.

*   Legal ops: ! ~ & ^ | + << >>

*   Max ops: 5

*   Rating: 2

*/

int negate(int x) {

return ~x+1;

}

在补码中有这样一个定理:

对于数x来说,-x = ~x + 1.

我们考虑用数学归纳法来证明这个式子:

(1)考虑只有2位的补码。此时,只有[00] = 0 、[01] = 1 、[10] = -2 、[11] = -1这四个数,容易发现-x = ~x + 1.

(2)现假设此结论对于拥有k位的补码成立.

(3) 下面证明此结论对于拥有k + 1位的补码成立。

由于篇幅与表达所限,只提供证明思路如下:

需要利用假设(2)的条件。需分别讨论:对于k + 1位的补码,当其最高有效位(即符号位)分别为0、1时的情况。

参考资料

[1]《深入理解计算机系统》(第3版). Randal E. Bryant, David R.O’Hallaron 著.

[2] 博客:SSD06 Exercise02 个人解答.

c语言相反数补码,C语言中补码的整数运算特性相关推荐

  1. java 大整数编程_Java编程--RSA算法中的大整数运算

    Java编程–RSA算法中的大整数运算 RSA原理浅析 RSA是利用陷门单向函数实现的,其安全基础依赖于大整数的分解问题的难解性 算法过程 为了加深对RSA算法的了解,接下来通过简单的一个例子来分析一 ...

  2. c语言中%只能用于正数吗,C语言中%只能用于整数运算的运算符?

    B)若a是实型变量,C程序中允许以下赋值a=10:因此,实型变量中允许存放整型数 我想问,A为什么错?D为什么对啊?如printf(%%dn,a);这样子,就用于了%d了?怎么会只能用于整数运算?展开 ...

  3. stm32c语言arctan函数,超高速的反正切算法,纯整数运算

    回复: 112 超高速的反正切算法,纯整数运算 (132093786) 出0入0汤圆 电梯直达 发表于 2017-3-14 14:52:38 | 只看该作者 |倒序浏览 |阅读模式 本帖最后由 s1j ...

  4. 原码、反码、补码,计算机中负数的表示

    原码:将一个整数,转换成二进制,就是其原码.如单字节的5的原码为:0000 0101:-5的原码为1000 0101.反码:正数的反码就是其原码:负数的反码是将原码中,除符号位以外,每一位取反.如单字 ...

  5. 位运算获取相反数详解

    位运算有很多化腐朽为神奇的操作,取相反数就位列其中,我们总是用[取反+1]来求相反数,就如同公式一样使用,可否曾仔细琢磨过其中的原理呢? 为什么[取反+1]是相反数? 在数学中,互为相反数的两数相加为 ...

  6. 首师大附中OJ系统 0007 相反数的问题

    相反数的问题 这里可以快速跳转 试题要求 试题分析 试题代码 试题要求 难度级别:A: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 小雅刚学 ...

  7. c语言补码计算方法,探讨C语言学习中补码计算方法.doc

    探讨C语言学习中补码计算方法 探讨C语言学习中补码计算方法 摘要:补码是C语言学习中的一个重点和难点,如何能够快速准确地计算出各个数的补码值得我们探讨.本文从补码的意义入手,采用逆向逻辑思维,分别提出 ...

  8. c语言中的原码反码补码,c语言中的原码 反码 补码

    陈独秀的秘密 所谓原码就是前面所介绍的二进制定点表示法,即最高位为符号位,"0"表示正,"1"表示负,其余位表示数值的大小. 反码表示法规定:正数的反码与其原码 ...

  9. c语言为什么要用补码,什么是补码为什么要用补码

    什么是补码为什么要用补码? 1.计算机中为什么使用补码形式: 电脑里面的数值用补码来表示,一方面是为防止0有2个编码,另外是为了将减法运算用加法运算表示出来,以达到简化电路的作用(有负数的概念,减法也 ...

最新文章

  1. displaysettings.java_Android设置系统开机自动永不休眠
  2. php井字游戏,python实现井字棋游戏
  3. Android热补丁技术—dexposed原理简析(手机淘宝采用方案)
  4. Linux centos7 Linux网络相关、firewalld和netfilter、netfilter5表5链介绍、iptables语法
  5. 机器学习笔记:误差的来源(bias variance)
  6. 计算机考研:计算机网络五大考点解析
  7. cp文件服务器,docker容器与物理机的文件传输—docker cp命令
  8. [剑指offer]面试题7:用两个栈实现队列
  9. 深度-图像风格变换【二】
  10. RNN-GRU-LSTM变体详解
  11. AI创业营第三期拉开帷幕:生存训练+思想盛宴
  12. 岁月是把杀猪刀时光不止催人老
  13. java具有回收垃圾的作用吗_Java中垃圾回收功能
  14. JVM调优-JVM调优实践一
  15. java服务器 c 服务器_c 编写服务器
  16. 同花顺 python量化交易_开启量化第一步!同花顺iFinD数据接口免费版简易操作教程...
  17. 三调 图斑地类面积_国土三调APP-GIS,地理信息,综合管网,地下空间,给水排水,通信管网,燃气管网,智慧旅游,...
  18. (最新)Win7安装配置IIS7.5详细图文教程 (一)
  19. diamond运算符
  20. CAD定数等分不显示等分点

热门文章

  1. Lucene索引详解
  2. 拜日式精准引导词_拜日式的教学引导词(转)
  3. css 第二个孩子选择器,详解CSS3选择器:nth-child和:nth-of-type之间的差异
  4. 2021年焊工(初级)模拟考试及焊工(初级)实操考试视频
  5. smart原则计算机二级,制定目标时的SMART原则不包括什么_常见问题解析
  6. 软件 pt 测试,软件测试(p网pt)完整版.ppt
  7. 关于财务报表的相关案例实现(一)
  8. 官方将文件size转换为MGB字符串
  9. MinGW、MSYS、Cygwin、Git Bash Shell
  10. Java实现 LeetCode 543 二叉树的直径