二进制整数的加,减运算

前言

在平时的编程中,当进行整数运算时,经常会遇到一些奇怪的结果,比如两个正数相加出现了负数,两个负数相加出现了正数,这些都是因为数值表示的有限性导致的.来看一个案例:

int a = 0x7FFFFFFF;

int b = 0x7FFFFFFF;

Console.WriteLine(a);

Console.WriteLine(b);

Console.WriteLine(a+b);

我这是C#的代码,我告诉你输出结果吧:-2

程序中的a和b都是很大的正整数,结果它们相加会得到一个负数.

再看看用C语言写的代码:

#include <stdio.h>

int main(){

int a = 0x7FFFFFFF;

int b = 0x7FFFFFFF;

printf("%d\n",a);

printf("%d\n",b);

printf("%d\n",a+b);

}

输出结果也是-2

这是咋个回事呢?下面跟随你单哥咱们一起探个究竟!

无符号加法

记得小时候杂学的加法把?我们都是使用的图画表示,就是上面是加数下面是加数,左边一个加号,逢十进一,然后下面一条横线,横线下面是结果.想起来了吧

对于二进制整数来说,其实是可以用这种最原始的方式去计算,由此也可以认识到,二进制整数的加法也是非常简单的.只不过与我们平时的十进制算法有一个最大的区别,那就是我们在计算机当中进行计算时,结果的位数都是有限制的.因此我们计算完成后,可能需要对结果进行截断操作.

前面一节咱们刚说了有关阶段的内容,那么明显这里就这样用上了.这里使用的时候有一个前提,我们可以假设是进行w位的二进制运算,那么在运算之后的实际结果也一定是w位的.这里有两种情况,一种结果依然是w位的,也就是w+1位为0.第二种则是到达了w+1位,这个时候我们需要将结果截断到w位.(明白不?就是3+3和9+9的案例,一个是6,6是一位,一个是18,18是两位,还不明白,我建议你从事别的专业吧,计算机可能不太适合你...)

第一种情况属于正常的加法运算,对于第二种来说,我们根据上一张的结论,假设完整的结果为sum,则实际的结果最终为sum mod 2w。

在书中给出了一个公式,它得到了这个结果的一个前提是两个操作数都满足小于2w,它与我们上面取模的结果其实是一样的。

再稍微解释一下,对于第一种情况,x+y < 2w,则sum mod 2w是与sum一致的。对于第二种来说,当 2w =< x+y < 2w+1,对 x + y 进行2w的取模运算,与 x + y - 2w是等价的。

无符号的非

无符号的加法会形成一个阿贝尔群,这意味着无符号的加法满足一些特性.比如说可交换,可结合等等.在这个群中,单位元为0,那么每一个群中的元素,就是每一个无符号数u,都会拥有一个逆元u-1,满足 u+ u-1 = 0。这个结论的来源是,对于w位上的无符号运算来讲,倘若两个无符号数的加法运算结果为2w,也就是1后面跟w个0,此时截断之后的结果则为0。

从以上的简单分析,我们可以很容易的得到一个无符号数的逆元满足以下公式(公式中的左边就是我写的u-1,由于图中的符号在博文中不好编辑,所以我以u-1替代)。

补码加法

对于不骂的加法来讲,我们会建立在无符号假发的基础上来进行,这么做的一个重要前提是,他们的位表示都是一样的.

其实补码加法就是先按照无符号加法进行运算,而后再进行无符号和有符号的的转换.因此我们根据上面的结论可得,对于两个补码编码的有符号数来说,他们进行加法运算的最终结果为,假设实际的无符号结果为sum,那么最终的实际结果为:

U2Tw(sum mod 2w)。

上面这个结论看起来很简单,但是实际上它的运算结果还是比较复杂的,树中给出了四种情况的分析,采用数学推导和证明的方式来说明.

与无符号加法不同的是,这里会出现三种结果,一种是正常的结果,一种是正溢出,一种数负溢出.

对于正溢出的时候,我们的结果与无符号数类似,取模之后等价于减去2的w次方.而当负溢出的时候,则刚好相反,取模之后的结果等价于加上2的w次方.更直观的,由于我们最终科表示的补码范围在-2w-1(包含)到2w-1之间,所以我们总是要试图将最终的实际结果报纸在这个范围之内.于是我们可以直观的得到下面的结果

补码的非

对于补码来说,它同样的与无符号有一样的特性,也就是对于任意的一个w位的补码t来说,他都有唯一的逆元t的-1次方,是的t+t的-1次方=0.

一个w位的补码数范围在-2的w-1次方(包含)到2的w-1次方之间,直观的可以看出,对于不等于-2的w-1次方的补码数x来说,他的逆元就是-x.而对于-2的w-1次方来说,他的二进制位表示为1后面跟着w-1个0,我们需要找到一个数与其相加之和结果为0.

这种时候我们需要考虑的是,如果是-x,也就是2的w-1次方,则它的位表示需要w+1位,是不存在.因此我们需要考虑溢出的情况,对照上面的公式2.14,负溢出的时候需要加上2的w次方,因此-2的w-1次方的逆元就是-2的w次方+2的w-1次方=-2的w-1次方,也就是他本身.

综上,最终我们可以得到补码的逆元满足以下公式(这里与上面一样,公式左边是我们所说的逆元t的-1次方).

二进制整数的减法

减法运算器是是可以由加法运算替代的,我们上面已经介绍过了无符号和补码的非,其实很多CPU是没有减法运算器的,他们都是将减数进行逆运算以后送入加法器,然后进行加法运行,这样得出来的结果就是减法运算最终的结果.

比如我们考虑一种简单的情况,当w=4时的无符号减法运算,对于5-4这个减法运算来说,我们可以由5+4的-1次方(其中4的-1次方是4的逆元的意思,不是四分之一的意思)来替代这个减法运算.

为了更加直观,咱们一起来算一下,首先4的逆元根据上面的公式可以得到4的-1次方=2的4次方-4=12.呢么我们现在需要对5和12进行加法运算,他们的位分别表示为0101和1100,结果为10001,就是十进制17的位表示.不过由于我们的w=4,因此截断之后结果为0001,就是十进制的1.最终结果得到5-4=1.

对于5-4来说,是考虑的结果为正的情况.或许有人认为会对结果为负或者说无符号数移除的情况下有疑问,因此咱在这里对这种情况也做了一个简单的介绍.我们考虑一个简单的计算0-1,我们可以得到1的-1次方=2的4次方-1=15.此时对0和15进行加法计算,他们的位表示分别为0000和1111,结果为1111.

看到这里估计有人会有疑问了,这是怎么回事,0-1=15?

当然不是,,,这个结果其实是正确的.考虑使用补码编码来解析1111这个位表示,它代表的值就是-1.15是1111这个位表示在无符号编码情况下的解析结果.

因此这里有一个公式,就是对于两个整数x和y来说,x-y=x+y的-1次方,这个公式代表的意义是位表示,而不是实际的数值.

小结

主要讲解了二进制整数的加法运算,还有剪发的简单介绍.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-11 05:51:07

二进制整数的加,减运算的相关文章

浮点加减运算中左规右规问题

当尾数用二进制表示时,浮点规格化的定义是尾数M应满足:  1/2   ≤  |M|<1 显然对于正数而言,有M = 00.1φφ-φ:对于负数,其补码形式为11.0φφ-φ(即-0.0*******,左归).这样,当进行补码浮点加减运算时,只要对运算结果的符号位和小数点后的第一位进行比较:如果它们不等,即为00.1φφ-φ或11.1φφ-φ,就是规格化的数:如果它们相等,即为00.0φφ-φ或11.0φφ-φ,就不是规格化的数,在这种情况下需要尾数左移以实现规格化的过程,叫做向左规格化.规则是:

Leetcode 592.分数加减运算

分数加减运算 给定一个表示分数加减运算表达式的字符串,你需要返回一个字符串形式的计算结果. 这个结果应该是不可约分的分数,即最简分数. 如果最终结果是一个整数,例如 2,你需要将它转换成分数形式,其分母为 1.所以在上述例子中, 2 应该被转换为 2/1. 示例 1: 输入:"-1/2+1/2" 输出: "0/1"  示例 2: 输入:"-1/2+1/2+1/3" 输出: "1/3" 示例 3: 输入:"1/3-1/

[Swift]LeetCode592. 分数加减运算 | Fraction Addition and Subtraction

Given a string representing an expression of fraction addition and subtraction, you need to return the calculation result in string format. The final result should be irreducible fraction. If your final result is an integer, say 2, you need to change

字符串大数加减运算问题

这里自己利用STL模板中的容器和链表实现字符串大数加减运算. 1 #include<iostream> 2 #include<vector> 3 #include<list> 4 using namespace std; 5 6 list<char> Input_Number(list<char> ilist)//输入链表字符串,以‘#’作为结束符 7 { 8 cout<<"please Input string ,end

C_BigDecimal_加减运算

首先举个例子说说思路: 输入str1:1.341   str2:11.2  在C语言中存储字符串的直接是字符型数组,strlen(str)代表字符串的长度(那个小数点也要算的),则str1,str2的长度为5和4.而浮点数加减运算时遵循从右往左计算,所以首先要使得字符串格式化.找到并返回存储字符串中小数点的下标,str1,str2小数点位置下标为1和2,然后用字符串长度减去即可求出小数点后的位数差,通过循环将短的加0补齐,为1.341,11.200,最后按最长长度循环将字符从右依次赋值给自定的c

linux date 加减运算

在linux shell编程中,经常用到日期的加减运算 查看时间: [[email protected] ~]# date Fri Sep  2 13:12:56 CST 2016 修改时间: [[email protected] ~]# date -s "1980-01-01 00:00:00" Tue Jan  1 00:00:00 CST 1980 其实date命令本身提供了日期的加减运算 非常方便.例如:得到昨天的时间date +%Y%m%d --date="-1 d

让文本框支持加减运算的实现方法

一个网页表单效果,让表单内的文本框支持加减运算,不过你要按正确的运算式输入,要不然它没有那么智能哦,比如输入1+5,文本框旁边会显示计算结果,这要归功于JavaScript的功能. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns=&

Oracle 如何对时间进行简单加减运算

在我们用dbms_job包进行定时Job的时候,需要设置时间间隔,所以需要知道时间的基本加减方法. SQL> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'; 会话已更改. SQL> select sysdate, sysdate+1/24, sysdate +1/1440, sysdate + 1/86400 from dual; --分别是加一小时,一分钟,一秒钟 SYSDATE SYSDATE+1/24 SYSDATE+

Oracle日期的加减运算

无论是DATE还是timestamp都可以进行加减操作.可以对当前日期加年.月.日.时.分.秒,操作不同的时间类型,有三种方法: 1 使用内置函数numtodsinterval增加小时,分钟和秒2 加一个简单的数来增加天3 使用内置函数add_months来增加年和月 例:对当前日期增加一个小时:SQL> select sysdate, sysdate+numtodsinterval(1,'hour') from dual ; SYSDATE             SYSDATE+NUMTOD