没有合适的资源?快使用搜索试试~ 我知道了~
java解惑doc
4星 · 超过85%的资源 需积分: 0 3 下载量 5 浏览量
2012-11-09
18:22:05
上传
评论
收藏 831KB DOC 举报
温馨提示
试读
64页
java 解惑
资源推荐
资源详情
资源评论
Java 谜题 1——表达式谜题
谜题 1:奇数性
下面的方法意图确定它那唯一的参数是否是一个奇数。这个方法能够正确运转
吗?
奇数可以被定义为被 整除余数为 的整数。表达式 计算的是 整除
时所产生的余数,因此看起来这个程序应该能够正确运转。遗憾的是,它不能;
它在四分之一的时间里返回的都是错误的答案。
为什么是四分之一?因为在所有的 数值中,有一半都是负数,而 方
法对于对所有负奇数的判断都会失败。在任何负整数上调用该方法都回返回
,不管该整数是偶数还是奇数。
这是 对取余操作符()的定义所产生的后果。该操作符被定义为对于
所有的 数值 和所有的非零 数值 ,都满足下面的恒等式:
换句话说,如果你用 整除 ,将商乘以 ,然后加上余数,那么你就得到了
最初的值 。该恒等式具有正确的含义,但是当与 的截尾整数整除操作
符相结合时,它就意味着:当取余操作返回一个非零的结果时,它与左操作数
具有相同的正负符号。
当 是一个负奇数时,等于 而不是 , 因此 方法将错误地返回
。为了防止这种意外,请测试你的方法在为每一个数值型参数传递负数、
零和正数数值时,其行为是否正确。
这个问题很容易订正。只需将 与 ! 而不是与 比较,并且反转比较的含
义即可:
"!
如果你正在在一个性能临界(# )环境中使用 方法,
那么用位操作符 $%&(')来替代取余操作符会显得更好:
'"!
总之,无论你何时使用到了取余操作符,都要考虑到操作数和结果的符号。该
操作符的行为在其操作数非负时是一目了然的,但是当一个或两个操作数都是
负数时,它的行为就不那么显而易见了。
谜题 2:找零时刻
请考虑下面这段话所描述的问题:
(# 在一家汽车配件商店购买了一个价值)*! 的火花塞,但是他钱包中都是
两美元一张的钞票。如果他用一张两美元的钞票支付这个火花塞,那么应该找
给他多少零钱呢?
下面是一个试图解决上述问题的程序,它会打印出什么呢?
+,-
#.--/0
.1#***!! *!
你可能会很天真地期望该程序能够打印出 !*2!,但是它如何才能知道你想要打
印小数点后两位小数呢?
如果你对在 &*.- 文档中所设定的将 类型的值转换为字符
串的规则有所了解,你就会知道该程序打印出来的小数,是足以将 类
型的值与最靠近它的临近值区分出来的最短的小数,它在小数点之前和之后都
至少有一位。因此,看起来,该程序应该打印 !*2 是合理的。
这么分析可能显得很合理,但是并不正确。如果你运行该程序,你就会发现它
打印的是 !*3222222222222222。
问题在于 * 这个数字不能被精确表示成为一个 ,因此它被表示成为最
接近它的 值。该程序从 中减去的就是这个值。遗憾的是,这个计算
的结果并不是最接近 !*2 的 值。表示结果的 值的最短表示就是
你所看到的打印出来的那个可恶的数字。
更一般地说,问题在于
并不是所有的小数都可以用二进制浮点数来精确表示的。
如果你正在用的是 &45*! 或更新的版本,那么你可能会受其诱惑,通过使用
工具来设置输出精度的方订正该程序:
拙劣的解决方案——仍旧是使用二进制浮点数
.1#**6*67*!! *!
这条语句打印的是正确的结果,但是这并不表示它就是对底层问题的通用解决
方案:它使用的仍旧是二进制浮点数的 运算。浮点运算在一个范围很
广的值域上提供了很好的近似,但是它通常不能产生精确的结果。
二进制浮点
对于货币计算是非常不适合的,
因为它不可能将 !*88或者 ! 的其它任何次
负幂——精确表示为一个长度有限的二进制小数
解决该问题的一种方式是使用某种整数类型,例如 或 -,并且以分为单
位来执行计算。如果你采纳了此路线,请确保该整数类型大到足够表示在程序
中你将要用到的所有值。对这里举例的谜题来说, 就足够了。下面是我们用
类型来以分为单位表示货币值后重写的 语句。这个版本将打印出正
确答案 2! 分:
.1#**!! !66
解决该问题的另一种方式是使用执行精确小数运算的 9-&#。它还可以
通过 &9+ 与 .:;&<+=>$; 类型进行互操作。这里要告诫你一点:
一定要用
构造器,而千万不要用
。
后一个构
造器将用它的参数的“精确”值来创建一个实例:?9-&#*将返回一
个表示
!*!!!!!!!!!!!!!!!5555@5A3A!353@B!B5B!5C
5 的 9-&#。通过正确使用 9-&#,程序就可以打印出我们所期
望的结果 !*2!:
#D*#,*9-&#
+,-
#.--/0
.1#**?9-*!!6*
?9-*!6
这个版本并不是十分地完美,因为 并没有为 9-&# 提供任何语言上
的支持。使用 9-&# 的计算很有可能比那些使用原始类型的计算要慢一
些,对某些大量使用小数计算的程序来说,这可能会成为问题,而对大多数程
序来说,这显得一点也不重要。
总之,
在需要精确答案的地方,要避免使用
和
;对于货币计算,
要使用
、
或
。
对于语言设计者来说,应该考虑对小数运
算提供语言支持。一种方式是提供对操作符重载的有限支持,以使得运算符可
以被塑造为能够对数值引用类型起作用,例如 9-&#。另一种方式是提
供原始的小数类型,就像 +9; 与 E;= 所作的一样。
谜题 3:长整除
这个谜题之所以被称为长整除是因为它所涉及的程序是有关两个 - 型数值整
除的。被除数表示的是一天里的微秒数;而除数表示的是一天里的毫秒数。这
个程序会打印出什么呢?
;-&
#.--/0
F->=+G.HE<GH&$IBC!C!!!!
!!!
F->=;;=.HE<GH&$IBC!C!!!!
.1#**>=+G.HE<GH&$I>=;;=.HE<GH&$I
这个谜题看起来相当直观。每天的毫秒数和每天的微秒数都是常量。为清楚起
见,它们都被表示成积的形式。每天的微秒数是(B 小时天C! 分钟小时
C! 秒分钟!!! 毫秒秒!!! 微秒毫秒)。而每天的毫秒数的不同之处只
是少了最后一个因子 !!!。
当你用每天的毫秒数来整除每天的微秒数时,除数中所有的因子都被约掉了,
只剩下 !!!,这正是每毫秒包含的微秒数。
除数和被除数都是 - 类型的,- 类型大到了可以很容易地保存这两个乘
积而不产生溢出。因此,看起来程序打印的必定是 !!!。
遗憾的是,它打印的是 5。这里到底发生了什么呢?
问题在于常数 >=+G.HE<GH&$I 的计算“确实”溢出了。
尽管计算的结果适合
放入
中,并且其空间还有富余,但是这个结果并不适合放入
中。这个
计算完全是以
运算来执行的,并且只有在运算完成之后,其结果才被提升
到
,而此时已经太迟了:计算已经溢出了,它返回的是一个小了
倍
的数值。从
提升到
是一种拓宽原始类型转换(
),它保留了(不正确的)数值。这个值之后被
!"#$
整除,而
!"#$
的计算是正确的,因为它适合
运算。这样整除的结果就得到了
%
。
那么为什么计算会是以 运算来执行的呢?因为所有乘在一起的因子都是
数值。当你将两个 数值相乘时,你将得到另一个 数值。&
不具有目
标确定类型的特性,这是一种语言特性,其含义是指存储结果的变量的类型会
影响到计算所使用的类型。
通过使用 - 常量来替代 常量作为每一个乘积的第一个因子,我们就可以
很容易地订正这个程序。这样做可以强制表达式中所有的后续计算都用 - 运
作来完成。尽管这么做只在 >=+G.HE<GH&$I 表达式中是必需的,但是在两
个乘积中都这么做是一种很好的方式。相似地,使用 - 作为乘积的“第一个”
数值也并不总是必需的,但是这么做也是一种很好的形式。在两个计算中都以
- 数值开始可以很清楚地表明它们都不会溢出。下面的程序将打印出我们所
期望的 !!!:
;-&
#.--/0
F->=+G.HE<GH&$IB;C!C!!!!
!!!
F->=;;=.HE<GH&$IB;C!C!!!!
.1#**>=+G.HE<GH&$I>=;;=.HE<GH&$I
这个教训很简单:
当你在操作很大的数字时,千万要提防溢出——它可是一个
缄默杀手。
即使用来保存结果的变量已显得足够大,也并不意味着要产生结果
的计算具有正确的类型。当你拿不准时,就使用 - 运算来执行整个计算。
语言设计者从中可以吸取的教训是:也许降低缄默溢出产生的可能性确实是值
得做的一件事。这可以通过对不会产生缄默溢出的运算提供支持来实现。程序
可以抛出一个异常而不是直接溢出,就像 $ 所作的那样,或者它们可以在需
要的时候自动地切换到一个更大的内部表示上以防止溢出,就像 ; 所作的那
样。这两种方式都可能会遭受与其相关的性能方面的损失。降低缄默溢出的另
一种方式是支持目标确定类型,但是这么做会显著地增加类型系统的复杂度
谜题 4:初级问题
得啦,前面那个谜题是有点棘手,但它是有关整除的,每个人都知道整除是很
麻烦的。那么下面的程序只涉及加法,它又会打印出什么呢?
<#1
#.-/0-
.1#**@B55B@
从表面上看,这像是一个很简单的谜题——简单到不需要纸和笔你就可以解决
它。加号的左操作数的各个位是从 到 5 升序排列的,而右操作数是降序排列
的。因此,相应各位的和仍然是常数,程序必定打印 CCCCC。对于这样的分析,
只有一个问题:当你运行该程序时,它打印出的是 AAAA。难道是 对打
印这样的非常数字抱有偏见吗?不知怎么的,这看起来并不像是一个合理的解
释。
事物往往有别于它的表象。就以这个问题为例,它并没有打印出我们想要的输
出。请仔细观察 操作符的两个操作数,我们是将一个 类型的 @B5 加
到了 - 类型的 5B@ 上。请注意左操作数开头的数字 和右操作数结尾的
小写字母 之间的细微差异。数字 的水平笔划(称为“臂(#)”)和垂直笔
剩余63页未读,继续阅读
资源评论
- zhangtsi2012-11-11内容不多,但是很深刻
叶子的翅膀
- 粉丝: 98
- 资源: 29
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功