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

今天看《程序设计语言概念》(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

转载于:https://www.cnblogs.com/antineutrino/p/4211284.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php注入教程,php注入点构造代码实例详解

php注入代码,方便注入测试把下面保存成 Test.asp代码如下:$mysql_server_name "localhost";$mysql_username "root";$mysql_password "password";$mysql_database "phpzr"; //??ݿ??$connmysql_connect( $mysql_se…

unity3d-小案例之角色简单漫游

准备资源 我这里从网上下载一个角色模型,里面有一组动画。有站立、奔跑、杀怪等 我们来实现角色的前后左后移动,即键盘上的WSDA键,这里因为没有行走的动画。索性就用奔跑代替了!! 暂时先不计较代码冗余的情况。因为我也…

PopupWindow在项目中的使用 并指定位置及加入动画效果

2019独角兽企业重金招聘Python工程师标准>>> 项目做到一期收尾阶段,突然要用到弹出窗口,于是自然而然的就想起了PopupWindow这个组件了,才发现平时用的少,对这个组件几乎是完全无知的状态啊。 于是恶补一番 现在放出学…

PHP新浪乐居面试题,一个朋友去新浪乐居面试时的面试题(PHP)

以下是他回忆当时的笔试题一、1、有如下HTML:1)用js取得________方法取得该对象;2)用________属性取得属性title的属性值;3)用________方法取得属性sina_title的属性值;2、php中对数组序列化和反序列化的函数分别是______和______…

Nodejs从小工到专家系列(一)

前言 从小工到专家系列为从头开始学习Nodejs,但它并不是教程,我会整理一些应该特别注意或深入理解的知识点,当然也会涉及常用的库,比如Express,mongoose,bluebird.. Nodejs特点: 单线程 优点: 没有死锁存在没有线程上下文交换所产生的性能开销缺点: 无法利用多核错误会引起整个…

php 图片无法删除,php如何删除上传的图片

php删除上传的图片的方法:首先检查上传文件是否在允许上传的类型;然后获取图片的完整路径;最后通过“unlink(“uppic/”.$img);”方法删除图片即可。简单的PHP上传图片和删除图片示例代码分享一例简单的PHP上传图片和删除图片示例代码&#x…

textbox 和textera 文本框多行后不能拉伸

加一个样式 "style "resize:none"转载于:https://www.cnblogs.com/q101301/p/4213298.html

Linux中断(interrupt)子系统之五:软件中断(softIRQ)

转自:http://blog.csdn.net/droidphone/article/details/7518428 软件中断(softIRQ)是内核提供的一种延迟执行机制,它完全由软件触发,虽然说是延迟机制,实际上,在大多数情况下,它与普…

php 克隆对象,php中对象的复制与克隆

* 对象的复制与克隆* 1.默认情况下,对象是引用传递(实际上是对象标识符的复制,后面会详细说)* 2.也就是说二个对象变量实际上是引用的是同一个对象* 3.如果要创建一个新的对象,必须使用clone关键字来克隆当前对象* 4.当使用clone关键字时,如果类中有__clone()会自动调用* 5.__c…

贝克汉姆-囚

转载于:https://www.cnblogs.com/andyxl/p/4215954.html

Androida规划nt打包

1.准备工作 (1)首先安装好ant工具 (2)生成keystore 在jdk的bin文件夹下 输入keytool -genkey -alias android.keystore -keyalg RSA -validity 20000 -keystore android.keystore 按操作输入就可以,记住password。 &am…

php5.3+for+linux,Centos 安装 nginx + php5.3

Centos 安装 nginx php5.3,点开查看详情。 #查看系统版本信息cat /etc/issue uname -a#设置时区 rm -rf /etc/localtime ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime#使用ntpdate同步时间 yum install -y ntp ntpdate -u pool.ntp.org date#Centos 安…

PHP Uploadify+jQuery.imgAreaSelect插件+AJAX 实现图片上传裁剪 仿微博头像上传功能

http://blog.csdn.net/as66t/article/details/11688217 http://blog.mc-zone.me/article/226#comment-2991转载于:https://www.cnblogs.com/ymj0906/p/4221967.html

[树结构]平衡二叉树AVL

平衡二叉树是一种二叉排序树,其中每一个节点的左子树和右子树的高度至多等于1,平衡二叉树又称为AVL树。 将二叉树节点的左子树深度减去右子树深度的值称为平衡因子BF,平衡二叉树上所有节点的平衡因子只可能是-1,0或者1。 距离插入点最近的&am…

dedecms php5.4 无法退出后台,PHP5.4版本织梦dedecms后台退出空白的解决方法

你是否遇到过PHP5.4版本织梦dedecms后台退出空白的问题,有没有解决呢?没有解决思绪,就来看看我这篇文章吧。解决办法:打开include/userlogin.class.php找到:function exitUser(){ClearMyAddon();session_unregister($this->ke…

C++构造函数/析构函数 设置成private的原因

C构造函数/析构函数 设置成private的原因 标签(空格分隔): c/c 将构造函数,析构函数声明为私有和保护的,那么对象如何创建? 已经不能从外部调用构造函数了,但是对象必须被构造,应该如…

R语言学习笔记(4)

第四章&#xff1a;基本数据管理 一 贯穿整章的示例 二 变量的创建、重编码和重命名 三 日期值与缺失值 四 数据类型和类型转换 五 数据集的排序、合并与取子集 一 贯穿整章的示例&#xff08;leadership&#xff09; 代码4-1 1 > manager<-c(1,2,3,4,5)2 > date<…

php substr_replace 中文乱码,php substr_replace替换字符串一些实例_PHP教程

substr_replace与str_replace有一点像就是直接把字符串替换一部份了&#xff0c;下面小编来给各位同学介绍一下操作方法。substr_replace() 函数把字符串的一部分替换为另一个字符串。用法substr_replace(string,replacement,start,length)注意当字符串包含中文时&#xff0c;不…

发布《Linux工具快速教程》

发布《Linux工具快速教程》 阶段性的完成了这本书开源书籍&#xff0c;发布出来给有需要的朋友&#xff0c;同时也欢迎更多的朋友加入进来&#xff0c;完善这本书&#xff1b; 本书Github地址&#xff1a;https://github.com/me115/linuxtools_rst 在线阅读 缘起 Linux下有很多…

java6:流程控制

Java 流程控制&#xff1a;顺序分支循环分支&#xff1a;if(布尔表达式){语句块}else{语句块}尽量使用肯定条件&#xff0c;减少else&#xff0c;减少嵌套package day06; import java.util.Scanner; public class Demo01 {public static void main(String[] args) {Scanner con…