谜题3:长整除

这个谜题之所以被称为长整除是因为它所涉及的程序是有关两个long型数值整除的。被除数表示的是一天里的微秒数;而除数表示的是一天里的毫秒数。这个程序会打印出什么呢?


public class LongDivision{

    public static void main(String args[]){

        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;

        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;

        System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);

    }

}

这个谜题看起来相当直观。每天的毫秒数和每天的微秒数都是常量。为清楚起见,它们都被表示成积的形式。每天的微秒数是(24小时/天60分钟/小时60秒/分钟1000毫秒/秒1000微秒/毫秒)。而每天的毫秒数的不同之处只是少了最后一个因子1000。

当你用每天的毫秒数来整除每天的微秒数时,除数中所有的因子都被约掉了,只剩下1000,这正是每毫秒包含的微秒数。

除数和被除数都是long类型的,long类型大到了可以很容易地保存这两个乘积而不产生溢出。因此,看起来程序打印的必定是1000。

遗憾的是,它打印的是5。这里到底发生了什么呢?

问题在于常数MICROS_PER_DAY的计算“确实”溢出了。尽管计算的结果适合放入long中,并且其空间还有富余,但是这个结果并不适合放入int中。这个计算完全是以int运算来执行的,并且只有在运算完成之后,其结果才被提升到long,而此时已经太迟了:计算已经溢出了,它返回的是一个小了200倍的数值。从int提升到long是一种拓宽原始类型转换(widening primitive conversion),它保留了(不正确的)数值。这个值之后被MILLIS_PER_DAY整除,而MILLIS_PER_DAY的计算是正确的,因为它适合int运算。这样整除的结果就得到了5。

那么为什么计算会是以int运算来执行的呢?因为所有乘在一起的因子都是int数值。当你将两个int数值相乘时,你将得到另一个int数值。Java不具有目标确定类型的特性,这是一种语言特性,其含义是指存储结果的变量的类型会影响到计算所使用的类型。

通过使用long常量来替代int常量作为每一个乘积的第一个因子,我们就可以很容易地订正这个程序。这样做可以强制表达式中所有的后续计算都用long运作来完成。尽管这么做只在MICROS_PER_DAY表达式中是必需的,但是在两个乘积中都这么做是一种很好的方式。相似地,使用long作为乘积的“第一个”数值也并不总是必需的,但是这么做也是一种很好的形式。在两个计算中都以long数值开始可以很清楚地表明它们都不会溢出。下面的程序将打印出我们所期望的1000:


public class LongDivision{

    public static void main(String args[ ]){

        final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;

        final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;

        System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);

    }

}

这个教训很简单:当你在操作很大的数字时,千万要提防溢出——它可是一个缄默杀手。即使用来保存结果的变量已显得足够大,也并不意味着要产生结果的计算具有正确的类型。当你拿不准时,就使用long运算来执行整个计算。

语言设计者从中可以吸取的教训是:也许降低缄默溢出产生的可能性确实是值得做的一件事。这可以通过对不会产生缄默溢出的运算提供支持来实现。程序可以抛出一个异常而不是直接溢出,就像Ada所作的那样,或者它们可以在需要的时候自动地切换到一个更大的内部表示上以防止溢出,就像Lisp所作的那样。这两种方式都可能会遭受与其相关的性能方面的损失。降低缄默溢出的另一种方式是支持目标确定类型,但是这么做会显著地增加类型系统的复杂度

原文地址:https://www.cnblogs.com/yuyu666/p/9840284.html

时间: 2024-10-18 20:43:19

谜题3:长整除的相关文章

《Java解惑》读书笔记

 摘选自<Java解惑>一书,之前整理了部分,一直没看完,最近为了督促自己每天读点这本书,决定一天至少更新一个谜题的内容,欢迎讨论. 欢迎关注技术博客http://blog.sina.com.cn/u/1822488043 Java解惑读书笔记 谜题1:奇数性 取余操作的定义: ( a / b ) * b + ( a % b ) = a 其中(a/b)是java运算的结果,也就是a/b是一个整数,比如3/2=1. 所以当取余操作返回一个非零结果的时候,它与左操作数具有相同符号. 请测试你的

[变]C#谜题(1-10)表达式篇

[变]C#谜题(1-10)表达式篇 最近偶然发现了<Java谜题>,很有意思,于是转到C#上研究一下. 本篇是关于表达式的一些内容. 谜题1:奇数性(负数的取模运算) 下面的方法意图确定它那唯一的参数是否是一个奇数.这个方法能够正确运转吗? 1 public static bool isOdd(int i) 2 { 3 return i % 2 == 1; 4 } 奇数可以被定义为被2 整除余数为1 的整数.表达式 i % 2 计算的是 i 整除 2时所产生的余数,因此看起来这个程序应该能够正

Java进阶1(Java 谜题)

1. //判断奇偶数 public static boolean isOdd(int i){ return i % 2 != 0; } 2. //减法精确 public static BigDecimal jian(String s1,String s2){ BigDecimal jian=new BigDecimal(s1). subtract(new BigDecimal(s2)); return jian; } 3. //长整除算法 public static void  chu(){ f

表达式之谜

以下内容全部来自<java解惑> 1.奇数性 这个谜题主要是提醒我们要注意如何来判断一个数是不是奇数 比如: i % 2 == 1 这个是否可以成功判断? 如果i是负数呢?显然会是-1 所以我更喜欢:i % 2 != 0 还有一种看起来比较high的:i&1 != 0 2.找零时刻 这个谜题是货币计算的,由于浮点数在计算机中并不会精确保存,所以,有时候难免有误差,因此问题也就产生了 如:System.out.println(2.00-1.10); 输出的不是你想象的0.90,而是一个近

学好数学能让程序员的水平更高

I've been working for the past 15 months on repairing my rusty math skills, ever since I read a biography of Johnny von Neumann. I've read a huge stack of math books, and I have an even bigger stack of unread math books. And it's starting to come tog

【转】程序员怎样学数学

I've been working for the past 15 months on repairing my rusty math skills, ever since I read a biography of Johnny von Neumann. I've read a huge stack of math books, and I have an even bigger stack of unread math books. And it's starting to come tog

[转] 程序员怎样学数学

Source:http://article.yeeyan.org/view/pluto/2365 --------------------------------------------------------------------- 读后感: 高中的时候数学成绩还不错,150分的卷子基本能保持在135以上.但是总感觉我的数学思维和数学修养仍然没什么提高.NUAA自招失败的经历让我彻底发现了这一点.大一学了一年的高数,又被繁杂的公式折磨得死去活来. 总感觉真正的数学不应该是这样的.但是真正的数

程序与数学 转自网络

I've been working for the past 15 months on repairing my rusty math skills, ever since I read a biography of Johnny von Neumann. I've read a huge stack of math books, and I have an even bigger stack of unread math books. And it's starting to come tog

自己写的一个分页控件类(WinForm)

using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; namespace xYuanShian.ControlLibrary { /// <summary> /// 翻页控件 /// </summary> pu