计算机中整数加法满足结合律吗

今天看《程序设计语言概念》(Concepts of Programming Language),第七章“结合性”一节中有这么一段:

某些计算机中的整数加法不具有结合性。例如,假设一个程序要计算“A + B + C + D”,其中A、C是很大的正数,B、D是绝对值很大的负数。在这种情况下,将B加到A并不会导致溢出,但将C加到A就会溢出。B和D与此类似。

这段话很好理解,因为只要是程序员,整数计算可能会溢出是基本的常识。但这段话只谈到计算的中间结果发生溢出的情况。如果不考虑中间结果而将重点放在最终结果上,计算顺序是否依然会对结果产生影响呢?也就是说,计算机中的加法满足结合律吗?即:

(a + b) + c

是否一定等于

(a + c) + b

呢?

首先,如果每个中间结果以及最终结果都没有溢出,可以肯定必然是满足结合律的,否则就是计算机自身有错误。

如果中间结果发生了溢出会怎样?我们不妨编写一个简单的程序验证一下。这里我们选择四个整数,a和c是两个是很大的正数,b和d是两个很小的负数(即绝对值很大的负数)。

int a =  2147483392; //0x7fffff00;
int b = -2147479553; //0x80000fff;
int c =  2146500592; //0x7ff0fff0;
int d = -2147421968; //0x8000f0f0;
int sum1 = ((a + b) + c) + d;
int sum2 = (a + b) + (c + d);
int sum3 = (a + c) + (b + d);
System.out.println("((a + b) + c) + d=" + sum1);
System.out.println("(a + b) + (c + d)=" + sum2);
System.out.println("(a + c) + (b + d)=" + sum3);

sum1为从左到右依次计算a+b+c+d的和;sum2先计算a+b和c+d,然后再计算二者的和;sum3则先计算a+c和b+d,然后再求和。通过代码可以看出,sum1和sum2的中间结果没有发生溢出,但sum3在计算a+c和b+d时都发生了溢出。下面来看看实际运行结果:

((a + b) + c) + d=-917537
(a + b) + (c + d)=-917537
(a + c) + (b + d)=-917537

可以看到无论顺序如何结果都是正确的。所以我们可以得出结论:

计算机中的整数加法运算满足结合律

这里还有一个问题,在上面的例子中,虽然中间结果发生了溢出,但最终结果是没溢出的。那如果最终结果也溢出了会怎么样?答案是不同计算顺序得到的结果仍然一样,只不过结果都是错的(都溢出了)。这是由计算机本身有限的精度导致的,和结合律无关,所以这种情况仍然认为是符合结合律的。你可以自己写个程序来验证这一点。

不过要注意,这个结论是有限定条件的,对现代大多数计算机系统来说该结论都成立,因为这些系统通常都采用“二进制补码”的方式来存储整数,而二进制补码的加法运算是符合结合律的。不满足结合律的例子也是有的,比如BCD码的加法运算。

------------------------------------------------------------------

写到这里,我想到了一个老题目:如何在不引入临时变量的情况下交换2个整数的值?一般来说,有2种方法可以做到,一种是使用加法,另一种是使用异或:

a = a + b;
b = a - b;
a = a - b;
a = a ^ b;
b = a ^ b;
a = a ^ b;

有人说第一种方法有问题,原因是将a和b相加时可能会溢出。如果你看了这篇文章,就会知道这种说法是错误的了——虽然a+b可能会溢出,但最后仍能得到正确的结果。要说缺点,只是它的效率比第二种要低一些。但话说回来,它的可读性却要优于第二种。

------------------------------------------------------------------

补码简介

下面简单介绍一下补码,如果对此不感兴趣或已比较熟悉请略过。二进制补码(Two‘s complement)采用“2^N的补”的方式存储的整数编码(其中N为整数的位长)。相比而言,另一种存储方式“反码”采用的是“1的补”,即逐位计算各个位的补(1的补为0,0的补为1,在二进制中这和取反是一样的),因此反码的英文名称为“One‘s complement”。

补码可以认为是对反码的改进,这不但因为补码中统一了“正零和负零”,还因为其计算也要比反码容易。最主要的一点是补码不用考虑进位(即溢出位),而反码则必须考虑。补码的另一个优点是其符号位同时也是计算位,因此计算时无需对正数和负数区别对待,这一点和反码一样。与补码和反码不同,原码则必须同时考虑数的正负和进位,因此很少有系统采用原码的方式来存储整数。

下面分别用补码和反码的方式来计算“10 - 1”,以此加深理解。

由于大多数计算机只实现了加法而没有减法,因此“10 - 1”实际上是转换为“10 + (-1)”来计算的。为了简单,这里假设整数只有8位。

补码的计算过程如下(-1的补码为“1111 1111”):

0000 1010

+ 1111 1111

——————

1 0000 1001

结果发生了溢出,产生了一个进位,对补码来说简单忽略即可,因此最后的结果为“9”。

注意这里的溢出属于正常溢出。相比之下,如果正数+正数结果为负数,或负数+负数结果为正数时,则说明发生了不正常的溢出。正常的溢出结果仍然是正确的(这正是补码的特性),而不正常的溢出得到的是错误的结果。

反码的计算过程为(-1的反码为“1111 1110”):

0000 1010

+ 1111 1110

——————

1 0000 1000

同样发生了溢出,但此时不能忽略进位,否则将得到错误结果“8”,因此还需要把进位加到结果上:

0000 1000

+                1

——————

0000 1001

得到最终结果“9”。

总结

最后再来简单总结一下。在大多数计算机系统中,整数的加法运算满足结合律。具体来说,如果最终结果没有溢出,即使计算过程的中间结果出现了溢出也不会影响最终结果。而如果最终结果本身就是溢出的,改变计算顺序仍然会得到一致的结果,这时候仍然认为是满足结合律的。

虽然这篇文章对实际编程可能用处不大,因为我们通常只需注意最终结果不要溢出即可,对中间过程无需在意。但这篇文章为这个结论提供了一定的理论支持,以帮助我们加深对计算机整数加法运算的理解。


参考资料:

有符号数的表示:http://en.wikipedia.org/wiki/Signed_number_representations

反码:http://en.wikipedia.org/wiki/Ones%27_complement

补码:http://en.wikipedia.org/wiki/Two%27s_complement

BCD码:http://en.wikipedia.org/wiki/BCD_code

时间: 2024-10-17 07:15:04

计算机中整数加法满足结合律吗的相关文章

计算机中的整数(原码、反码、补码)

系统中所有的信息——包括磁盘文件.存储器中的程序存储器中存放的用户数据以及网上落上传送的数据,都是由一串位表示的.区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文.比如,在不同的上下文中,一个同样的字节序列可能表示一个整数.浮点数.字符串或者机器指令. 计算机中的整数可以分为无符号整数和有符号整数两种类型.无符号整数不存在正负之分,在计算机中以其二进制真值的形式存放.而有符号整数由于有正负数的区分,表示相对复杂. 计算机中的符号数有三种表示方法,即原码.反码和补码.三种表示方法均有符号

计算机中如何实现除数是2的幂次的除法【转载自CSDN】

前言: 本来是在看汇编里面的数据条件传送指令,做习题的时候看着这么一道有关于2的幂次方除法的题目.结果傻眼了,又尼玛不会了.........第二章看的时候就稀里糊涂的,看了几遍也没看太懂,这回又涉及到了 ,发现再回来看还是容易一点.所以写此博文,方便日后复习. 我今天遇到的问题如下: 问题: 除法,在我们平时的算数运算中,结果总是向0的方向舍入的,但是在计算机中,舍入的方式有所不同.在大多数的机器中,除法要比乘法还有加法这些运算都要慢很多倍,计算机中对于2的幂次这种数很是敏感,因为计算机当中用到

负数在计算机中的表示 Byte-128

http://blog.csdn.net/njuitjf/article/details/4585247 今天,老大让我调查一个浮点数转换为整数的问题.自己就查了些资料,顺便复习一下原码.反码和补码. 原码:将一个整数,转换成二进制,就是其原码.如单字节的5的原码为:0000 0101:-5的原码为1000 0101. 反码:正数的反码就是其原码:负数的反码是将原码中,除符号位以外,每一位取反.如单字节的5的反码为:0000 0101:-5的原码为1111 1010. 补码:正数的补码就是其原码

计算机中的原码,反码,补码与移码

在计算机内,定点数有3种表示法:原码.反码和补码. 原码:就是二进制定点表示法,即最高位为符号位,0表示正,1表示负,其余位表示数值的大小 反码:正数的反码与其原码相同:负数的反码是对其原码逐位取反,但符号位除外.       原码10010=反码11101(10010,1为符号位,故为负) 补码:正数的补码与原码相同,负数的补码是对其原码逐位取反,但符号位除外,然后整个数加1 如果补码的符号位为0,则表示一个正数,其原码就是补码如果补码的符号位为1,则表示一个负数 移码:移码与补码的关系: [

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

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

【华为OJ】【039-无线OSS-高精度整数加法】

[华为OJ][算法总篇章] [华为OJ][039-无线OSS-高精度整数加法] [工程下载] 题目描述 在计算机中,由于处理器位宽限制,只能处理有限精度的十进制整数加减法,比如在32位宽处理器计算机中, 参与运算的操作数和结果必须在-231~231-1之间.如果需要进行更大范围的十进制整数加法,需要使用特殊 的方式实现,比如使用字符串保存操作数和结果,采取逐位运算的方式.如下: 9876543210 + 1234567890 = ? 让字符串 num1="9876543210",字符串

C/C++中整数与浮点数在内存中的表示方式

在C/C++中数字类型主要有整数与浮点数两种类型,在32位机器中整型占4字节,浮点数分为float,double两种类型,其中float占4字节,而double占8字节.下面来说明它们在内存中的具体表现形式: 整型: 整型变量占4字节,在计算机中都是用二进制表示,整型有无符号和有符号两种形式. 无符号变量在定义时只需要在相应类型名前加上unsigned 无符号整型变量用32位的二进制数字表示,在与十进制进行转化时只需要知道计算规则即可轻松转化.需要注意的是在计算机中一般使用主机字节序,即采用“高

计算机中负数表示法

问一个基本的问题. 负数在计算机中如何表示? 举例来说,+8在计算机中表示为二进制的1000,那么-8怎么表示呢? 很容易想到,可以将一个二进制位(bit)专门规定为符号位,它等于0时就表示正数,等于1时就表示负数.比如,在8位机中,规定每个字节的最高位为符号位.那么,+8就是00001000,而-8则是10001000. 但是,随便找一本<计算机原理>,都会告诉你,实际上,计算机内部采用2的补码(Two'sComplement)表示负数. 什么是2的补码? 它是一种数值的转换方法,要分二步完

简单理解信息在计算机中的表示

信息是一个很宽泛的概念,说大了是与物质和能量鼎立的自然界三要素,这里仅仅涉及到计算机中的信息: 众所周知,对计算机自身而言,所有信息都是0/1二进制形式: 作为JavaEE/Android程序员,在开发过程中有时会遇到字符编码,进制转换这样的基础问题,虽然依靠经验或者网络搜索能很快实现功能,但每次都感觉对基本概念理解得不是很透彻,不如把现有的理解记录下来,以备后用: 信息在计算机中大致分为控制信息和数据信息: 控制信息是计算机系统内部运转用到的控制命令,例如读写命令,中断信号,片选信号,复位信号