新書推薦:
《
知命不惧:从芝诺到马可·奥勒留的生活艺术
》
售價:NT$
505.0
《
Zemax光学设计从基础到实践
》
售價:NT$
602.0
《
全球化的黎明:亚洲大航海时代
》
售價:NT$
500.0
《
危局
》
售價:NT$
383.0
《
穿裙子的士:叶嘉莹传
》
售價:NT$
245.0
《
财富方程式
》
售價:NT$
352.0
《
知识社会史(下卷):从《百科全书》到“在线百科”
》
售價:NT$
454.0
《
我读巴芒:永恒的价值
》
售價:NT$
602.0
|
編輯推薦: |
一本专门为程序员而写的数学书,训练数学思维,增强职场竞争力
书中没有罗列晦涩难懂的数学公式和推导,而是代之以生动有趣的数学实例
读者不必精通高深的数学知识,只需要具备四则运算和基本的逻辑思维即可阅读
趣谈110个数学实例,并给出了33个具体的程序代码实现
内容通俗易懂,讲解娓娓道来,风格活泼自然,版式新颖,读来亲切自然
|
內容簡介: |
本书是一本专门为程序员而写的数学书,介绍了程序设计中常用的数学知识。本书门槛不高,不需要读者精通很多高深的数学知识,只需要读者具备基本的四则运算、乘方等数学基础知识和日常生活中的基本逻辑判断能力即可。本书拒绝枯燥乏味的讲解,而是代之以轻松活泼的风格。书中列举了大量读者都很熟悉,而且非常有趣的数学实例,并结合程序设计的思维和算法加以剖析,可以训练读者的数学思维能力和程序设计能力,进而拓宽读者的视野,增强职场竞争力。
本书共11章,分别介绍了数据的表示、神奇的素数、递归、排列组合、用余数进行数据分组、概率、复利、数理逻辑、推理、几何图形构造、统筹规划等程序设计中常用的数学知识,从而引导读者深入理解编程中的数学方法和思路。本书包含的实例有结绳记事、孪生素数、梅森素数、哥德巴赫猜想、阶乘、汉诺塔、斐波那契数列、乘法原理、加法原理、字符编码、密码长度、日历中的数学、心灵感应魔术、约瑟夫环、智叟分牛、百枚钱币鼓士气、庄家的胜率、中奖概率、用概率方法求π值、复利的威力、对折纸张、舍罕王的赏赐、三段论、选言推理、假言推理、关系推理、花盆摆放、残缺棋盘、丢失的线条、田忌赛马、背包问题等。
本书适合广大程序设计人员及数学爱好者阅读,尤其适合有一定程序设计经验,但还需要进一步加深对程序设计理解的人员阅读。本书对IT求职人员、信息学竞赛和大学生程序设计竞赛等参赛学员也有很好的参考价值。
|
關於作者: |
周颖 毕业于电子科技大学。高级程序员、某软件公司的技术总监。擅长C和C++语言,对数据结构和算法有深入的研究。长期从事行业软件设计和团队管理工作,已十年有余。有着丰富的IT架构设计经验和行业咨询经验。负责过多个大型软件项目的开发工作。
|
目錄:
|
第1章 数据的表示
1.1 一则童话
1.1.1 0和1的故事
1.1.2 0是什么都没有?
1.1.3 0的位置
1.1.4 程序中的
1.2 司空见惯的十进制数
1.2.1 远古的结绳记事
1.2.2 什么是十进制计数
1.2.3 为啥人类习惯十进制
1.2.4 十进制运算规则
1.2.5 十进制数的分解
1.2.6 20!等于多少
1.2.7 大整数构想
1.3 为啥要用二进制
1.3.1 人脑与电脑
1.3.2 二进制计数规则
1.3.3 简单的二进制运算规则
1.3.4 二进制数的分解
1.3.5 十进制数转换为二进制数
1.4 还有哪些进制
1.4.1 神奇的八卦:八进制
1.4.2 钟表使用的十二进制
1.4.3 半斤八两:十六进制
1.4.4 60年一个甲子:六十进制
1.4.5 各种进制之间的转换
1.4.6 二进制与八进制、十六进制的转换
第2章 神奇的素数
2.1 怎么判断素数
2.1.1 什么是素数
2.1.2 验证素数
2.1.3 寻找素数的算法
2.1.4 已被证明的素数定理
2.2 孪生素数
2.2.1 什么是孪生素数
2.2.2 孪生素数的公式
2.2.3 中国剩余定理
2.2.4 孪生素数分布情况
2.3 使用素数的RSA算法
2.3.1 什么是RSA
2.3.2 RSA算法基础
2.3.3 RSA算法实践
2.3.4 RSA应用:数字签名
2.3.5 RSA被破解的可能性
2.4 哥德巴赫猜想
2.4.1 哥德巴赫猜想是什么
2.4.2 数值验证
2.5 梅森素数
2.5.1 什么是梅森素数
2.5.2 已知的梅森素数列表
第3章 递归——自己调用自己
3.1 从前有座山,山里有座庙
3.1.1 老和尚讲的故事
3.1.2 德罗斯特效应
3.1.3 什么是递归
3.1.4 用递归能解决哪些问题
3.1.5 一个简单例子:求最大公约数
3.2 用递归计算阶乘
3.2.1 阶乘该怎么计算
3.2.2 阶乘的递归计算方法
3.2.3 递归的过程
3.2.4 递归的本质:缩小问题规模
3.3 汉诺塔
3.3.1 古老的传说
3.3.2 从两个盘考虑
3.3.3 找出递归结构
3.3.4 实现程序
3.3.5 究竟需要移动多少次
3.4 斐波那契数列
3.4.1 兔子的家族
3.4.2 从最初几月数据中找规律
3.4.3 斐波那契数列
3.4.4 神奇的魔八方
第4章 排列组合——让数选边站队
4.1 把所有情况都列出来
4.1.1 从0还是1开始
4.1.2 赛程安排
4.2 乘法原理
4.2.1 行程安排的问题
4.2.2 乘法原理适用条件
4.2.3 棋盘上棋子的放法
4.2.4 买彩票保证中奖的方法
4.3 加法原理
4.3.1 仍然是行程问题
4.3.2 总结出的加法原理
4.3.3 骰子出现偶数的次数
4.4 排列与组合的关系
4.4.1 排列
4.4.2 组合
4.4.3 排列与组合的联系
4.4.4 可重排列
4.5 计算机中的字符编码
4.5.1 ASCII码能表示的字符数量
4.5.2 能表示更大范围的编码
4.6 密码的长度
4.6.1 容易破解的密码
4.6.2 多长的密码才安全
4.6.3 密码中使用的字符数量也很关键
第5章 余数——数据分组
5.1 复习小学的余数
5.1.1 自然数的余数
5.1.2 余数的性质
5.1.3 用余数进行分组
5.2 日历中的数学
5.2.1 n天后是星期几
5.2.2 下月的今天是星期几
5.2.3 10年后的“今天”是星期几
5.3 心灵感应魔术
5.3.1 一个小魔术
5.3.2 魔术师是怎么猜出来的
5.4 奇偶校验
5.4.1 不可靠的网络传输
5.4.2 用奇偶校验检查错误
5.5 吕洞宾不能坐首位
5.5.1 座位安排
5.5.2 试排座位找规律
5.5.3 西方的约瑟夫环
5.5.4 用数学方法解约瑟夫环
5.6 智叟分牛
5.6.1 遗产分配难题
5.6.2 智叟给出的分配方案
5.6.3 分配原理
第6章 概率——你运气好吗
6.1 初中学习过的概率
6.1.1 谁先开球
6.1.2 用程序模拟抛硬币
6.1.3 什么是概率
6.1.4 必然事件与不可能事件
6.1.5 概率的基本性质
6.2 百枚钱币鼓士气
6.2.1 狄青的计谋
6.2.2 全为正面的概率是多少
6.2.3 必然还是偶然
6.3 庄家的胜率是多少
6.3.1 一个看似公平的游戏
6.3.2 庄家能赢钱吗
6.3.3 庄家盈利比率
6.3.4 游戏参与者获胜的概率
6.4 你能中奖吗
6.4.1 想中大奖吗
6.4.2 计算中奖概率
6.5 渔塘中有多少条鱼
6.5.1 该怎么估算渔塘中的鱼
6.5.2 用概率来估算
6.5.3 用概率方法求π值
第7章 翻一番是多少
7.1 翻番的概念
7.1.1 什么是翻番
7.1.2 翻倍的概念
7.1.3 计算倍数和番数
7.2 复利的威力
7.2.1 利润——投资回报
7.2.2 认识单利
7.2.3 认识复利
7.2.4 计算投资回报的程序
7.2.5 忘还钱的信用卡
7.2.6 爱因斯坦的72法则
7.3 对折纸张
7.3.1 有趣的问题:纸张对折
7.3.2100米长的纸能对折几次
7.3.3 计算对折次数的程序
7.4 一棋盘的麦子
7.4.1 舍罕王的赏赐
7.4.2 需要多少麦粒
7.5 折半法的运用
7.5.1 翻番的逆运算
7.5.2 找出假硬币
7.5.3 编写程序找出假硬币
7.5.4 折半法在查找中的应用
第8章 数理逻辑——非此即彼
8.1 逻辑的重要性
8.1.1 模棱两可的表述
8.1.2 肯定或否定
8.1.3 程序中的逻辑判断
8.2 命题逻辑
8.2.1 什么是命题
8.2.2 命题的逻辑形式
8.2.3 简单命题
8.2.4 复合命题
8.2.5 复合命题的联结词
8.3 布尔逻辑
8.3.1 逻辑或
8.3.2 逻辑与
8.3.3 逻辑非
8.3.4 逻辑异或
8.3.5 二进制位运算
8.4 考虑到各种可能了吗
8.4.1 逻辑重叠的实例
8.4.2 逻辑遗漏的实例
8.4.3 用数轴确定边界
8.5 用卡诺图简化逻辑函数
8.5.1 什么是卡诺图
8.5.2 三变量卡诺图
8.5.3 四变量卡诺图
8.5.4 卡诺图化简
8.5.5 卡诺图中的相邻
第9章 推理——逻辑的应用
9.1 演绎推理
9.1.1 认识演绎推理点
9.1.2 三段论
9.1.3 选言推理
9.1.4 假言推理
9.1.5 关系推理
9.1.6 演绎推理综合实例
9.2 归纳推理
9.2.1 什么是归纳推理
9.2.2 完全归纳推理
9.2.3 不完全归纳推理
9.3 足球比赛的得分
9.3.1 粗心的记分员
9.3.2 从已有数据推算出比分
第10章 几何图形构造
10.1 花盆摆放问题
10.1.1 10盆花摆成5行,每行4盆
10.1.2 转变思路,找出答案
10.1.3 升级问题(10盆花摆10行,每行3盆)
10.2 残缺的棋盘能补上吗?
10.2.1 被切割的棋盘
10.2.2 能拼接出残缺棋盘吗
10.3 线条哪里去了?
10.3.1 神奇的魔术
10.3.2 解析丢失的线条
10.4 图形剪拼
10.4.1 均分三角形
10.4.2 拼接正方形
第11章 统筹规划
11.1 认识统筹规划
11.1.1 田忌赛马
11.1.2 为什么会赢
11.2 生活中的统筹规划
11.2.1 匆忙的早晨
11.2.2 如何节约运输成本
11.3 著名的背包问题
11.3.1 什么是背包问题
11.3.2 用递归程序解决背包问题
11.3.3 用穷举法解决背包问题
|
內容試閱:
|
第1章 数据的表示
数学古称算学,是中国古代科学中一门重要的学科。根据中国古代数学发展的特点,可以分为5个时期,分别是萌芽、体系的形成、发展、繁荣和中西方数学的融合。
在数学的不同发展阶段,对于数据的表示都有一些不同的形式。从远古的结绳记数,到现在用计算机等现代科技设计记数,数的表示形式也在逐步演化。
本章主要介绍数据的各种表示形式,包括各种进制及进制之间的转换。
1.1 一
则 童 话
根据我们所学的知识可知道,数据通常是用0、1、2、3、4、5、6、7、8、9这些数来表示,由这些数的不同组合表示现实生活中各种各样的数据。首先来看这个数列中的前两个数:0和1,从通常意义来说,0就是什么也没有,真的是这样吗?对程度员来说不应该这样理解。
先来看这样一个问题,0和1谁大?
1.1.1 0和1的故事
在数学王国里,胖子0与瘦子1常常为了谁大而争执不休。瞧!今天,这两个小冤家狭路相逢,彼此之间又展开了一场舌战。
瘦子1抢先发言:“哼!胖胖的0,你有什么了不起?就像100,如果没有我这个瘦子1,你这两个胖0有什么用?”
胖子0不服气了:“你也甭在我面前耍威风,想想看,要是没有我,你就只是一个光杆呢?”
“哟!”1不甘示弱,“你再神气也不过是表示什么也没有,看!1+0还不等于我本身,你哪点儿派得上用场啦?”
“去!1×0结果也还不是我,你1不也同样没用!”0针锋相对。
“你……”1顿了顿,随机应变道,“不管怎么说,你0就是表示什么也没有!”
“这就是你见识少了。”0不慌不忙地说,“你看,日常生活中,气温0度,难道是没有温度吗?再比如,直尺上没有我作为起点,哪有你1呢?”
“再怎么比,我始终比你大。”1信心十足地说。
听了这话,0更显得理直气壮地说:“嘿嘿,你的大小还得我说了算,我站你左边,你就成0.1,我站你右边你就是10。怎么样?我可让你放大10倍,也可让你缩小10倍!”
眼看着胖子0与瘦子1争得脸红耳赤,谁也不让谁,一旁观战的其他数字们都十分 着急。
这时,9灵机一动,上前做了个暂停的手势:“你俩都别争了,瞧你们,1、0有哪个数比我大?”
“这……”胖子0、瘦子1哑口无言。
这时,9才心平气和地说:“1、0,其实,只要你们站在一块,不就比我大了吗?”
1、0面面相觑,半晌才搔搔头笑了。“这才对嘛!把自己的位置放正,就能起到应有的作用”。9语重心长地说。
从以上故事可看出以下两点:
q 0并不表示什么都没有。
q 数的大小与所处的位置有关系。
下面就来讨论这两个问题。
1.1.2 0是什么都没有?
通常意义上,0表示“没有”的意思。例如,“2012年过去了,可我的收获为零!”这就表示在2012年没有收获。
但是,0真表示什么都没有吗?
其实,0不仅表示什么都没有,它还有更丰富的内涵。例如,0度并不是没有温度,而是表示温度为0度,比零下1度高,比1度低,如图1-1所示。
图1-1
在日常生活的常用语中,也有很多用0来表示的,如“很多女孩子都喜欢吃零食”,这里的“零食”并不是表示没有“食”,如图1-2所示。
图1-2
“为了增加收入,改善生活,很多程序员在业余时间都会接点零活来做。”这里的“零活”并不是没有“活”。
其实,在数学上,0也并不是表示没有。例如,8和8.0相等吗?其含义相同吗?
看起来在小数点后添加一个0是没有意义的,不过,其含义实际是不相同的。在近似数表示中,数字8表示数据只精确到个位,如7.9、8.2等数精确到个位都表示为8。而8.0表示数据精确到十分位,如8.02、7.99等数精确到十分位都表示为8.0。所以,从这个角度来看,8和8.0是不相等的。
1.1.3 0的位置
从“0和1的故事”可看出,当0所处的位置不同时,其含义也不一样。如前面说的8和8.0,当把0放在小数点后面时,从绝对值方面来看,两个数是相等的,但从近似数来看,小数点后多了一个0,其表示的含义也就不一样了。
那么,在小数点左侧添加0呢?如果在数的最左侧添加0,无论添加多少个0,数的大小都不变。
但是,如果在数的中间插入0,数的位置与数的大小关系就很明显了,如在18的中间插入一个0,得到的是108,很明显,其大小差别很大。
对于18,表示十位为1,个位为8,也就是说,表示18这个数有1个10,8个1。而108,表示百位为1,十位为0,个位为8,即表示有1个100,0个10,8个1,这时的0是一个占位符,把1从十位挤到百位。
而如果在紧邻小数点的左侧添加0,则数据会扩大10倍。
1.1.4 程序中的0
在电子技术中,0一般表示低电平,1为高电平。在逻辑计算中,0一般表示逻辑假(False),1为逻辑真(True)。在数值运算中,0与平常数学中0的含义相同。
在程序中,数据0有什么含义呢?
1.未赋值的变量为0?
在不同的程序设计语言中,对于未赋值变量的处理不一样。
对于Basic类的程序语言,如QB(Quick Basic,简环QB)、VB(Visual Basic,简称VB),如果数值型变量未赋初值,则其初始值为0。例如,有以下VB程序代码:
Private Sub Test
Dim i As Integer
MsgBox "变量 i=" i, , "变量初始值"
End Sub
在以上VB代码中,声明了变量i,但未对其进行赋值。虽然未进行变量赋值初始化,但VB编译器会自动将这类数值型变量初始化为0。因此,执行以上代码将显示如图1-3所示的对话框。
图1-3
对程序员来说,VB对变量进行初始化的方式很讨人喜欢,变量声明后就可以使用。但是,在.Net Framework中,其处理方式又不相同,例如,以下是VB.NET中的代码:
Private Sub Button1_Clicksender As
System.Object, e As System.EventArgs _
Handles Button1.Click
Dim i
MsgBox"变量 i=" i, , "变量初始值"
End Sub
以上代码并不会出错,但运行后得到的结果如图1-4所示。从这个结果可看出,在VB.NET中,如果变量使用之前未进行初始化,这时其值为空(并不为0)。
图1-4
其实,在Visual Studio开发环境中仔细观察代码,可看到在MsgBox函数中的变量i下方有一个波浪线,将鼠标指针指向变量i,可看到如图1-5所示的提示信息,提示变量i在赋值前被使用。
图1-5
对于C语言系列的程序设计语言(如C、C++、C#等),程序员就没那么幸运了,未初始化的变量编译器并不会将其初始化为0,而且不同编译系统可能会采用不同的处理方式。例如,有如下的C#程序:
private void button1_Clickobject sender, EventArgs e
{
int i;
MessageBox.Showstring.Format"变量 i={0}", i, "变量初始值";
}
以上的C#程序是没办法编译通过的。在Visual Studio开发环境中可以看到变量i下方有一条波浪线,将鼠标指针移到变量i上,可看到如图1-6所示的错误提示信息,提示使用了未赋值的局部变量i。
要想得到如图1-3所示的对话框,在C#中必须将变量i进行初始化,给变量赋值为0,修改后的代码如下:
图1-6
private void button1_Clickobject sender, EventArgs e
{
int i=0;
MessageBox.Showstring.Format"变量 i={0}", i, "变量初始值";
}
而在Dev-CPP环境中编写以下C语言程序:
int main
{
int i;
printf"变量i=%d",i;
getch;
return 0;
}
编译时不会提示错误,运行时则将显示类似图1-7所示的结果。
图1-7
虽然在程序中没有初始化变量i,但变量i却有一个值(图1-7中显示的是1976933940,下次运行该程序时可能又是另一个值),这是为什么呢?原来,在ANSI C中定义变量时,编译器将给该变量分配内存,但并不会将分配的内存初始化为0。这样,原来该内存区域中保存的是什么值,新指定的变量也就具有了什么值。在图1-7所示结果中,给变量i分配的内存中的值正好为1976933940,所以变量i也就具有了这个值。
2.数值0的类型转换
程序中经常会用到数据类型的转换,如将数值类型转换为字符串类型、将数值类型转换为布尔类型等。
将数值0转换为字符串0,这种转换很好理解,其显示的内容都是相同的0,只有在进行数值运算时才能体现出不同。
数值0转换为布尔类型是什么值呢?
在ANSI C中没有专门设置布尔类型,在进行逻辑运算时,将0值作为布尔值False,将非0值作为布尔值True。
在C#中,定义了Boolean类型,数值0转换为Boolean类型时得到的结果为False,非0值转换为Boolean类型时得到的结果为True。
3.除以0异常
我们在小学就学过:0可以做被除数,但不可以做除数。在程序中,当除数为0时,将出现异常。例如,有以下C代码:
int main
{
int Dividend,
Divisor,Result;
Dividend = 8;
Divisor = 0;
Result = Dividend
Divisor;
printf"%d%d=%d",Dividend,Divisor,Result ;
getch;
return 0;
}
当执行以上代码时,由于除数Divisor为0,将产生一个严重的错误,导致程序不能继续运行,如图1-8所示。
图1-8
在程序执行中如果遇到这种异常,将导致程序中断,但这不是我们所希望的。一个好的程序员应该考虑并处理程序中可能发生的各种异常,并捕获这些异常,然后给用户显示出一个友好的错误提示信息。不过,ANSI C中并没有提供异常捕获机制,因此需要程序员根据程序执行过程,主动去判断除数,以避免产生这种严重异常。例如,可将以上代码修改为以下形式:
int main
{
int Dividend, Divisor,Result;
Dividend = 8;
Divisor = 0;
ifDivisor==0{
printf"除数不能为0!";
}else{
Result = Dividend
Divisor;
printf"%d%d=%d",Dividend,Divisor,Result ;
}
getch;
return 0;
}
编译执行以上程序,将得到如图1-9所示的结果,提示了“除数不能为0!”,程序并没有进入严重异常状态。
图1-9
在异常捕获方面,C++、C#就要方便得多。例如,C#定义了很多异常(也包括DivideByZeroException异常),我们在程序中可以使用try…catch结构来捕获这些异常并进行处理。
1.2 司空见惯的十进制数
有没有想过,为什么6+8=14?
从小就这样学的呗!
对,我们小学就开始学“逢十进一,借一当十”,觉得很自然。这就是司空见惯的十进制计数法。
1.2.1 远古的结绳记事
远古时期,人类文明还没有得到发展,但是,“数学”却先于语言、文字而产生。这是因为人们在生活中用到数学的地方很多。例如,每个人捕获猎物的数量,应该怎么表示呢?首先想到的是双手10个手指。天长日久,人类在大自然的生存过程中,积累了更多的生存经验。随着人类征服自然、适应自然的能力逐步提高,捕获或养殖的动物数量也逐步增加,此时靠双手的十个手指来计数就不够了。从史料来看,此时人类进一步的做法是排石子、划道道等。此时,数数还不会,计算更是谈不上。
在我国民间有一种传说,认为伏羲氏始创了结绳记事的方法。结绳记事,是在绳子上打一个结来表示一个数,如图1-10所示,“事大大其绳,事小小其绳,结之多少,随物众寡”,这在当时所起的作用是非常大的。
随着人类文明的进步,人类将一只羊、两只羊、三只羊……这些具体的概念抽象化,得到了数字1、2、3……,只是当时的表现方式有所不同,如图1-11所示。从图中可看到,巴比伦数字类似于按数量摆放石子,而中国数字类似于画痕,罗马数字进一步抽象,用Ⅴ表示数字5,如果在其左侧有一竖,表示为4(=5-1),若在其右侧有一竖,表示为6(=5+1),右侧有两竖,表示为7(=5+2),依次类推。在罗马数字中用Ⅹ表示10,根据其左侧或右侧的竖线数量来表示低于10或大于10的数。现在罗马数字仍在很多地方使用。
图1-10
图1-11
阿拉伯数字则是现今国际通用的数字,最初由印度人发明,后由阿拉伯人传向欧洲,之后再经欧洲人将其现代化。正因阿拉伯人的传播,成为该种数字最终被国际通用的关键点,所以人们称其为“阿拉伯数字”。阿拉伯数字由0、1、2、3、4、5、6、7、8、9共10个计数符号组成。采取位值法,高位在左,低位在右,从左往右书写。借助一些简单的数学符号(小数点、负号等),这个系统可以明确地表示所有的有理数。为了表示极大或极小的数字,人们在阿拉伯数字的基础上还创造了科学记数法。
1.2.2 什么是十进制计数
正如本节开始时所说,十进制计数法是我们司空见惯的,从小学习的就是十进制。那么,什么是十进制计数?
十进制数基于位进制和十进位两条原则,即所有的数字都用10个基本的数字表示,满10进1,同时同一个数字在不同位置上所表示的数值大小不同,因此数字的位置非常重要。
十进制的基本数字是0、1、2、3、4、5、6、7、8、9。要表示这10个数字的10倍,就将这些数字左移一位,右侧用0补上空位,即可得到10、20、30、……90(0的10倍还是0),如图1-12所示。若要继续扩大10倍来表示数字,就继续左移数字的位置,然后在右侧用0补上空位,即100、200、300……。
图1-12
要表示一个数的十分之一,百分之一,千分之一,就将数字向右移,在左侧(小数点右侧)补上0,即可得到十分位(0.1)、百分位(0.01)、千分位(0.001),如图1-13所示。
图1-13
1.2.3 为啥人类习惯十进制
为什么我们从小学习的就是十进制,而不是更简单的二进制?
首先,看看我们的双手,我们有10根手指,如图1-14所示。从人类最初计数时起,首先想到的就是用双手的手指来计数,数满10个数再增加一双手,这样就产生了十进制。
图1-14
另一个很重要的方面,就是习惯。我们从小接受的教育就是使用十进制数进行计算,因此习惯了十进制数的运算。
二进制的运算规则比十进制简单,为什么不使用二进制呢?这是因为二进制的运算规则虽然简单,但是要表示一个较大的数据时,需要用很长一串数据,如十进制的50000写成二进制为1100 0011 0101 0000,一共需要16位,谁一眼能看出该数据的大小?
如果我们一直使用二进制,可能对二进制表示的数也能方便地识别出来,但是和十进制相比可以看出,十进制数比二进制数更简洁,更易识别。
而比十进制更大的进制(如十六进制),其运算规则复杂,更难以使用。
因此,在日常生活中是以十进制数为主。
1.2.4 十进制运算规则
十进制数的常用运算包括加、减、乘、除这4种,也称为四则运算。在初等数学中,当一级运算(加、减)和二级运算(乘、除)同时出现在一个算式中时,它们的运算顺序是先乘除,后加减。要改变这种运算规则,则需要通过括号,因为四则混合运算中,总是先计算括号内,然后再计算括号外。同一级运算顺序则是按从左到右的顺序进行。
在加、减、乘、除这4种运算中,加、减法互为逆运算,乘、除法互为逆运算,而乘法是加法的简便运算,如图1-15所示。
图1-15
1.加法
加法运算是把两个数合并成一个数的运算,可以将整数、小数、分数进行合并运算。在加法运算中,首先应当将相加的数从个位开始按位对齐,然后从个位开始(从右向左)逐位相加。加法运算时,两数(或多数)相加的和超过10时就向前一位进位,这种规则称为“逢10进1”,如图1-16所示。
2.减法
减法运算是已知两个加数的和与其中一个加数,求另一个加数的运算。在减法运算中,首先应当将相减的数从个位开始按位对齐,然后从个位开始逐位相减。如果对应位上被减数小于减数时,需向被减数前一位进行借位,借1当10,再和本位的数相加,得到一个超过10的数,再用这个数与减数进行运算即可,如图1-17所示。
图1-16
图1-17
3.乘法
乘法运算是求几个相同加数的和的简便运算。乘法运算比加、减法更复杂,不过,对于十进制数的乘法运算来说,只需要背熟九九乘法表,并按此规则逐位相乘,然后再将各位乘积进行累加,即可得到最终结果,如图1-18所示。
图1-18
4.除法
除法运算是已知两个因数的积与其中一个因数,求另一个因数的运算。
除法法则:除数是几位,先看被除数的前几位,前几位不够除,多看一位,除到哪位,商就写在哪位上面,不够商1时,要用0占位。除法可能会有余数,余数要比除数小。如果商是小数,商的小数点要和被除数的小数点对齐;如果除数是小数,要将其化成整数后再用整数的除法进行计算。
除法是乘法的逆运算。图1-18所示的乘法算式,可表示成如图1-19所示的除法 算式:
图1-19
1.2.5 十进制数的分解
十进制数由0~9这10个数字组成,依据数字所在位置决定数值的大小。数据的各位从右向左依次为个位、十位、百位、千位……。
如图1-20所示,个位的9表示有9个1,十位的8表示8个10,百位的7表示7个100,按这种方式,可将十进制数按图1-21所示方式进行分解。
图1-20
图1-21
仔细看图1-21所示数据的分解式,从右向左,每个数码都比其右侧的数大10倍,可将上式简写成图1-22所示的形式。
图1-22
十进制是我们从小就开始学的,以上这些规则都很简单,为什么还要在这里重复呢?因为程序员通过十进制的这些运算规则可推导出其他进制数的运算规则,也可设计解决更多的问题,例如大整数的运算问题。
1.2.6 20!等于多少
在设计大整数之前,我们先来看一个例子。以下是一个C语言程序,该程序中定义了一个计算整数阶乘的函数fact,在主函数中调用fact函数计算1~20各数的阶乘。
int factint f
{
int result=1,i;
fori=2;i=f;i++
{
result*=i;
}
return result;
}
int main
{
int f,r;
for f=1;f=20;f++
{
r=factf;
printf"%d!=%d\n",f,r;
}
getch;
return 0;
}
运行以上程序,得到如图1-23所示的结果。
图1-23
从图1-23中可以看出,14!的结果比13!的结果还小,肯定出问题了。为什么会出现这种错误呢?电脑连15的阶乘都计算不出来?
分析程序代码可看出,程序中使用的是int类型的变量,在C语言中,这种变量保存的数据范围为–2,147,483,648~2,147,483,647,而13!再乘以14,其结果已经超过int类型的表示范围(其实,13!的值应该为6,227,020,800,已经超过了int类型的表示范围),因此,数据就出错了,从17!、18!还可以看出其结果变成了负数。
既然知道了出错原因是由于数据类型导致的,那么,是不是将以上程序的int类型改变为位long类型,就可以计算更大数的阶乘了呢?理论上是这样,不过,由于ANSI C中规定,在字长为32位的计算机中,int类型和long类型都是32位。因此,这里将数据类型修改为long也不能解决问题。
在支持64位字长的C#系统中,long类型使用64位二进制位表示(8个字节),其表示的数据范围为–9,223,372,036,854,775,808~9,223,372,036,854,775,807。但是,这么大的数在阶乘面前也很快就被会填满,图1-24所示为使用C#计算各数阶乘的输出结果。
从图1-24中可看到,使用64位字长的long类型,20的阶乘也可以被正确表示出来了。但是更大的数呢?21!、22!的结果是多少?
图1-24
哦,My God!还是出错了,21的阶乘就变成负数了。这还是long类型的表示范围问题,long类型的表示范围为–9,223,372,036,854,775,808~9,223,372,036,854,775,807。
对于基本的整数类型,使用ulong(无符号长整型)类型来保存数据,其表示范围也只为0~18,446,744,073,709,551,615,再大的数就没办法表示了。那么,更大数的阶乘该怎么办呢?
1.2.7 大整数构想
在实际应用中,除了阶乘之外,还有很多地方需要使用到非常大的整数,而计算机程序设计语言对数据的表示范围总是有限的。因此,还得我们程序员自己想办法,设计一个能处理大整数的类,这个类应该能处理任意位数长度的整数。
根据本节前面对十进制数的分析,可以很容易地想到,可以在程序中用一个数组来表示大整数的各位,由于数组元素的多少只受计算机内存限制,因此,就可以处理任意长度的大整数了。
如图1-25所示,定义一个数组,然后将数据的各位分解到数组的各个元素中。
图1-25
这个数组只是大整数的一种表示形式,要处理大整数,还需要记录数据的正负、加减时的进位等。然后定义以下常用操作:
q 加法:将整数从个位开始(即数组的0号元素)进行累加。累加时还需判断是否要进位(逢10进1),因此,在累加时还需要将进位数进行累加。
q 减法:将整数从个位开始(即数组的0号元素)进行减法操作,若不够减还需要从前一位借位(借1当10)。被减数在进行减法操作时还需要减去被借的位。
q 乘法:按图1-18所列的乘法算式,可将乘数中的每一个数组元素与被乘数相乘,然后将结果累加,即可得到大整数相乘的结果。当然,在进行加法和乘法运算时也需要考虑进位的情况。
q 除法:除法的实现要麻烦一点。首先要考虑试商的问题,从被除数的高位开始与除数对齐,试商时用被除数的部分位减去除数,判断能减几次,就可商几。另外,除法还需要考虑余数问题。除法的过程如图1-26所示。
从图1-26的演算过程可看到,除法运算需要循环调用加法运算进行试算,然后再调用减法运算计算试商后的余数。
根据这个构想编写大整数处理函数,即可处理任意长度的整数(如可保存、计算长度为100位、200位甚至更多位整数的加、减、乘、除运算),不再局限于C语言所提供数据类型中有限的整数长度了。
有幸的是,在微软的.NET Framework 4(以及JAVA的JDK 1.5)中已经提供了一个大整数类型,可以处理任意长度的大整数。如果使用.NET Framework 4进行开发,就不用自己编写大整数类型了。当然,如果在ANSI C环境下编写程序,仍然可以按本节介绍的构思编写自己的大整数类型。
图1-26
1.3 为啥要用二进制
既然人类从远古时代就开始使用十进制数了,为啥还要用二进制呢?二进制有什么优势呢?下面我们一起走进二进制的世界。
1.3.1 人脑与电脑
通过人类的进化,以及人们长期的学习和训练,人脑的潜能可以被不断地发掘。吉尼斯世界纪录中记纸牌记得最多的是一名英国人,他只需看一眼就能记住54副洗过的扑克牌(一共有2808张牌)。还有人能记住圆周率小数点后的42,905位数字!可见,人脑的潜能是可以不断被挖掘的。
在生活中,人脑对很多事物都形成了条件反射,例如,对于数据10与9的大小,我们可以直接反应出10比9大。不过,由于没有通过相应的运算,仅凭人脑直觉反应得出的结果可能是不准确的。例如,对于像比较大的两个数99999999与100000000,要想看一眼就得出哪一个数据更大,就变得不太可能了,即使得出结论,可能也会有错误。为什么呢?这是因为数据的位数变多了,并且重复的数很多,人脑无法一下子反应出来。通常要数一下有多少位数,然后才能进行判断。
计算机(俗称的电脑)却不一样,对于任何操作,电脑都需要经过相应的运算,然后才能得出结果。不管是比较10与9,还是比较99999999与100000000,电脑都会按规定的算法进行运算,最后得出相应的结果。而电脑一旦得出结果,其结果肯定是准确的!
在电脑中,使用二进制来保存数据和编写程序。为什么选用二进制,而不选用人类已经熟练使用的十进制呢?
如果要让电脑使用十进制,首先,应该让电脑能识别出十进制中的10个数字。怎么识别10个数字呢?通常的考虑是,可以通过元器件中电压的高低水平来分别标识10个数字。假如最高电压为12V,那么10个数字中,每个数码可以分配的电压区间为1.33V,如图1-27所示。
图1-27
从图1-27可知,每个数之间的电压间隔小,如果外界干扰造成电压大幅变化,数据就不准确了(如本来电压为1.33V,可被识别为数字1,但是由于外界干扰,电压增加了1V,就变成2.33V了,这里距离2.67V更近,就可能被识别为数字2)。还有一个最大的问题,在硬件上要识别这10种状态,其电路结构将非常复杂。
当然,这里只是一种假设,实际应用中采用的是二进制。由于二进制数只有2个数码,电路就很简单了,因为具有两种稳定状态的元件(如晶体管的导通和截止,继电器的接通和断开,电脉冲电平的高低等)很容易被找到。
因此,在电脑中使用二进制主要有以下优点:
q 技术实现简单。电脑由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示。
q 运算规则简单。两个二进制数的和、积运算组合分别有3种规则,相比十进制数的运算规则来说非常简单(十进制的九九乘法表就有81种规则),有利于简化计算机内部结构,提高运算速度。
q 适合逻辑运算。逻辑代数是逻辑运算的理论依据,二进制只有两个数码,正好与逻辑代数中的“真”和“假”相吻合。
q 易于进行转换。二进制数与十进制数、八进制数、十六进制数之间的转换很方便。
q 抗干扰能力强。用二进制表示数据具有抗干扰能力强、可靠性高等优点。因为每位数据只有高低两个状态,当受到一定程度的干扰时,仍能可靠地分辨出它是高电平还是低电平(只分辨电平的高低,而不用识别具体电压值)。
知道电脑用二进制数来存储后,再来看电脑就简单了。电脑保存的数据用电路的两种状态表示,当数据很大时,只需要增加数据的位数就可以了。
当超大规模集成电路迅速发展起来后,电脑中就可以处理、存储海量数据信息了。并且,由电脑保存的数据保存周期长,不易丢失、损坏。而由于人类的认知及人的记忆会随时间出现遗忘等原因,要用人脑来存储、处理海量信息,就不太可能。
因此,很多人认为电脑比人脑强。其实,电脑本质上只能识别0和1这两种状态!而更复杂的功能,则是由人类对0和1这两种状态进行各种组合而得到的。
1.3.2 二进制计数规则
二进制的计数规则非常简单,只需要记住以下3点就行了:
q 基数为2。
q 只有2个数码,即0和1。
q 逢2进1,借1当2。
如图1-20所示,十进制数可以由多位组成,从右向左分别为个位、十位、百位、千位、万位……,与此类似,二进制数也可由多位组成,从右向左分别为1位、2位、4位、8位、16位……,如图1-28所示。
图1-28
为什么称为1位、2位、4位、8位……呢?其实,这是从十进制角度来看二进制的各位数得出的名称。根据二进制计数的规则,用二进制计数时,第一个数为0,第2个数为1(根据逢2进1的规则),接下来为10(所以第2位就是“2位”),继续下来依次为11、100(所以第3位就是4位)、101、110、111、1000……。
如表1-1所示是十进制数0~127用二进制表示的对应形式。从表1-1中可以看到,当十进制数为0和1这两种情况时,可用1位二进制来表示这两种状态;当十进制数在7以内时,可以使用3位二进制数来表示——随着十进制数的增大,对应的二进制数的位数也会增多,在表1-1中表示到十进制数127时就需要7位二进制位来表示了。
表1-1
十进制数0~127对应的二进制表示
十进制
二进制
十进制
二进制
十进制
二进制
十进制
二进制
0
0
32
10 0000
64
100 0000
96
110 0000
1
1
33
10 0001
65
100 0001
97
110 0001
2
10
34
10 0010
66
100 0010
98
110 0010
3
11
35
10 0011
67
100 0011
99
110 0011
4
100
36
10 0100
68
100 0100
100
110 0100
5
101
37
10 0101
69
100 0101
101
110 0101
6
110
38
10 0110
70
100 0110
102
110 0110
7
111
39
10 0111
71
100 0111
103
110 0111
8
1000
40
10 1000
72
100 1000
104
110 1000
9
1001
41
10 1001
73
100 1001
105
110 1001
10
1010
42
10 1010
74
100 1010
106
110 1010
11
1011
43
10 1011
75
100 1011
107
110 1011
12
1100
44
10 1100
76
100 1100
108
110 1100
13
1101
45
10 1101
77
100 1101
109
110 1101
14
1110
46
10 1110
78
100 1110
110
110 1110
15
1111
47
10 1111
79
100 1111
111
110 1111
16
1 0000
48
11 0000
80
101 0000
112
111 0000
17
1 0001
49
11 0001
81
101 0001
113
111 0001
18
1 0010
50
11 0010
82
101 0010
114
111 0010
19
1 0011
51
11 0011
83
101 0011
115
111 0011
20
1 0100
52
11 0100
84
101 0100
116
111 0100
21
1 0101
53
11 0101
85
101 0101
117
111 0101
22
1 0110
54
11 0110
86
101 0110
118
111 0110
23
1 0111
55
11 0111
87
101 0111
119
111 0111
24
1 1000
56
11 1000
88
101 1000
120
111 1000
25
1 1001
57
11 1001
89
101 1001
121
111 1001
26
1 1010
58
11 1010
90
101 1010
122
111 1010
27
1 1011
59
11 1011
91
101 1011
123
111 1011
28
1 1100
60
11 1100
92
101 1100
124
111 1100
29
1 1101
61
11 1101
93
101 1101
125
111 1101
30
1 1110
62
11 1110
94
101 1110
126
111 1110
31
1 1111
63
11 1111
95
101 1111
127
111 1111
1.3.3 简单的二进制运算规则
与十进制数的运算规则相比,二进制数的运算规则就简单多了。同样,二进制也可对数据进行加、减、乘、除这些基本的算术运算,另外,二进制数据还可进行逻辑运算。有关逻辑运算的内容需要另一个主题来介绍,下面先来看看二进制的算术运算是多么的简单。
1.加法
与十进制的加法相比,二进制的加法规则要简单得多。如果只考虑一位数相加的情况,在十进制加法运算中,加数和被加数都有0~9共10种可能,因此,会产生100种可能的情况(即使根据加法交换律将重复的运算过滤掉,也会有55种情况)。而二进制加法运算中,加数和被加数都只有两种可能,因此,只会有以下4种情况:
根据加法交换律,将第2、3种运算看作为一种运算,则二进制的加法运算就只有3种情况了。3比55!二进制的加法运算规则是不是要简单得多!
对于多位数的二进制相加,其运算规则与十进制相同,仍然会有进位的情况,只是进位时采用“逢2进1”的方式。例如,将十进制数25加上38,根据表1-1找出对应的二进制数,即可列出如图1-29所示的十进制数与二进制数加法的竖式。
图1-29
在图1-29所示的两种进制的加法运算中,左侧的十进制运算是按“逢10进1”的方式进位,右侧的二进制运算则是按“逢2进1”的方式进位。虽然是两种不同的数据表示方式,但运算的结果是一致的(十进制数65对应的二进制数为1000001)。
2.减法
二进制的减法运算规则也很简单,只有以下4种可能:
如图1-30所示为十进制和二进制减法的竖式,在运算时注意十进制是“借1当10”,而二进制则是“借1当2”。
图1-30
3.乘法
十进制数的乘法运算需要按“九九乘法表”法则进行,而二进制乘法的规则就简单多了,与加、减类似,也只有以下4种情况:
可以看出,只有当被乘数、乘数都为1时,结果才为1;当被乘数或乘数有一个为0时,相乘的结果就为0。
另外,二进制的乘法运算可以很简单地转化为加法运算。首先看如图1-31所示的乘法竖式,左边是十进制的乘法竖式,右边是二进制乘法竖式。
从右侧的乘法竖式可看出,当乘数某位为0时,这一位与被乘数相乘时各位都为0,当乘数某位为1时,这一位与被乘数相乘时得到的是被乘数对应的各位,只是需要将被乘数向左移动相应的位数。这样,二进制数的乘法就可以很简单地转化为“加法与移位”。
图1-31
4.除法
除法是乘法的逆运算,既然二进制的乘法表只有4项,则其除法表也对应有如下4项(其中除以0是无意义的):
看看图1-31中乘法的逆运算,如图1-32所示。
图1-32
从图1-32中可看出,二进制的除法运算也可简单地转化为“减法与移位”操作。
可以看出,在二进制中,加、减、乘、除算法都可转换为加法运算。对于减法运算,只需要将被减数设置为负数,就可将其转换为加法;对于乘法,则可使用“加法与移位”操作来完成;对于除法,则可使用“减法与移位”操作来完成。
既然比较复杂的乘法和除法运算能简单地转化为加、减法和移位操作,因此,电脑中就只需要设计一个加法器即可,这样就简化了电路设计。
1.3.4 二进制数的分解
在前面的二进制计数规则中曾说过,二进制数从右向左依次为1位、2位、4位、8位……,这是指二进制数中各位的意义。虽然只有0和1这两个数码,但是其所处的位置不同,表示数据的大小也不同。例如,有以下二进制数:
1011
这个二进制数共有4位,由3个1和1个0组成。同样的3个1,由于其处于不同的位置,这些1所表示的大小也是不同的,其所处的位置称为权。按从右向左的顺序各位的含义如下:
q 第1个1表示“1的个数”;
q 第2个1表示“2的个数”;
q 第3个0表示“4的个数”;
q 第4个1表示“8的个数”。
因此,二进制数1011由1个8、0个4、1个2、1个1组成。按各位的权列出的算式如下:
从这种按权展开式可看出,每个位的“权”表现为2的幂次关系,即相邻两位相同数码代表的值为2倍的关系(从右向左看,第2位的1是第1位的1的2倍)。
这种按权展开式可方便地将二进制数转换为十进制数。
1.3.5 十进制数转换为二进制数
将二进制数按权展开,就可以方便地将其转换为十进制数。那么,十进制数该怎么转换成二进制数呢?
十进制整数转换为二进制整数通常采用“除2取余,逆序排列”法。
具体做法是:用2整除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为0时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。
例如,将十进制数14转换为二进制数时,“除2取余”的转换过程如图1-33所示。
图1-33
通过图1-33所示的逐项除2取余方式,得到每次除以2的余数,最后将取得的余数按逆序排列,即将十进制数14转换为二进制数1110。将这个二进制数按权展开,又可得到十进制数14。
1.4 还有哪些进制
其实,除了我们常用的十进制数和电脑用的二进制数之外,生活中还有很多的计数进制,并且有很多的进制也在电脑中使用。
1.4.1 神奇的八卦:八进制
八卦最初是上古人们记事的符号,后被用为卜筮符号,古代常用八卦图作为除凶避灾的吉祥图案。因此,八卦也就被打上了封建迷信的标记。
1.从八卦说起
其实,八卦中隐含了二进制和八进制的概念。首先,八卦的最基本概念是阴和阳,这就相当于二进制中的0和1。在八卦图中用一根长实线代表阳,用一根中间断开的线代表阴,然后由3个这样的线条符号组成8种形式(相当于3位二进制数,可以表示8种状态),因此叫做八卦,如图1-34所示。
图1-34
在八卦中,每一卦形代表一定的事物。乾代表天、坤代表地、坎代表水、离代表火、震代表雷、艮(gèn)代表山、巽(xùn)代表风、兑代表泽。
经过几千年的发展,八卦被赋予了很多的含义,除了上面介绍的代表自然现象之外,还可以代表方位、家族、五行,还可以将卦图转换为二进制数。如表1-2所示就是八卦中各卦所代表的不同含义。
表1-2
八卦的含义
八卦名
八卦图像
自然
方位
二进制
乾
天
西北
111
兑
p align
|
|