当前位置: 首页 > 工具软件 > Puzzle Game > 使用案例 >

Java Puzzlers 之Puzzle 3: Long Division

百里鸿祯
2023-12-01

  Puzzle 3: Long Division

这个难题之所以叫做Long型除法,是因为它是关于两个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);

 

    }

}

 

Solution 3: Long Division

 

这个难题看起来没有什么问题。一天里的毫秒数和微秒数都是常量。显然,能直接看出他们表示的结果。一天中的微秒数为(24小时/*60分钟/小时*60/分钟*1000毫秒/*1000微秒/毫秒)。而一天中的毫秒数与之不同处仅仅在于缺少最后的1000因数。当你用一天中的毫秒数去除一天中的毫秒数时,所有的因数都被抵消掉,而仅仅剩下每毫秒中的微秒数:1000。除数和被除数都是long型,它们足够大以至于乘积不会溢出。看起来,程序应该输出1000。但是,不幸的是,它输出的为5。那么实际上它是怎么处理的呢?

问题在于计算每天的微秒常量时发生溢出。尽管long型可以有足够的空间去容纳计算的结果,但是这并适合于int型。整个的计算是以int型进行计算,仅仅计算完成后,其结果被转化为long型。到那时,已经太晚了:计算的结果已经溢出, 返回了一个缩小了200倍的数。从int型到long型的提升是一个扩宽的原始转化,它保存了一个错误的值。它在被毫秒数除,对于int型来说,这个过程是正确的计算。这个除法的结果就是5

为什么整个计算过程是用int型进行计算?因为所有的因数都是int型。当你使用两个int型相乘,你就可以得到另一个int型数。Java没有目标类型功能(一种参数类型语言功能,在这种功能中,结果保存的类型将影响计算的类型)。

很容易修改这个程序:在每个乘积的第一个因数都采用long型来替换int型。它强使表达式中后来的计算都按照long型进行计算。尽管只有在每天的微秒计算才有必要如此使用,最好还是在两个计算中都使用这种方式。类似的,这并不总是有必要在一个乘积中使用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一样自动的按要求匹配内部表示而避免溢出。另外一种方式就是支持目标类型来降低溢出的可能性。但是这样就增加了类型系统的复杂性。

 
 类似资料: