2012-03-26 228 views
9

应用程序开发团队的角度来看,处理整数溢出如999999 * 999999(result> Integer.MAX_VALUE)的常见做法是什么?常见的做法如何处理整数溢出?

可以让BigInt强制使用,禁止使用Integer,但这是一个好的/坏的想法?

回答

13

如果该整数溢出非常重要的,你可以定义自己的溢出醒目的操作,例如:

def +?+(i: Int, j: Int) = { 
    val ans = i.toLong + j.toLong 
    if (ans < Int.MinValue || ans > Int.MaxValue) { 
    throw new ArithmeticException("Int out of bounds") 
    } 
    ans.toInt 
} 

您可以用充实,你的库模式把这个进入运营商;如果JVM管理做正确逃生的分析,你不会得到太多的罚款,因为它:

class SafePlusInt(i: Int) { 
    def +?+(j: Int) = { /* as before, except without i param */ } 
} 
implicit def int_can_be_safe(i: Int) = new SafePlusInt(i) 

例如:

scala> 1000000000 +?+ 1000000000 
res0: Int = 2000000000 

scala> 2000000000 +?+ 2000000000 
java.lang.ArithmeticException: Int out of bounds 
    at SafePlusInt.$plus$qmark$plus(<console>:12) 
    ... 

如果不是极其重要,那么标准的单元测试和代码评审等,这些应该会在大多数情况下发现问题。使用BigInt是可能的,但会使算术速度下降100倍左右,如果您必须使用采用Int的现有方法,则无法为您提供帮助。

+0

嗨雷克斯,感谢您的及时回复!一个合理的解决方案,虽然它可能需要一些重新分解。考虑到复杂类型系统,篡改Integer基类将溢出检查标志添加到标准“运算符”函数有多难? – IODEV 2012-03-26 16:16:13

+0

顺便说一句,关于“”+?+“”,是否有标准的Scala命名转换应该使用? /提前致谢 – IODEV 2012-03-26 16:23:30

+0

@IODEV - 你可以添加一个包装类,但是你不能明智地篡改基类,因为它实际上映射到JVM中的'int'原语,因此有很多特殊的编译器魔术。 “?”的选择对我来说是任意的;以'+'开头会使运算符的优先级保持不变,我喜欢对称性(其他人做得足够好,尽管我不会称这是一个_convention_,但它至少是熟悉的),所以我在末尾添加了另一个'+' 。 '+ @'也会起作用。 – 2012-03-26 17:04:06

4

到目前为止,最常见有关整数溢出的做法是,程序员应该知道,这个问题存在,以观察他们可能发生的情况,并进行相应的检查或者重新安排数学,使溢出赢得不会发生,像做*(b/c)而不是(a * b)/ c。如果项目使用单元测试,它们将包括尝试强制溢出的情况。

我从来没有从一个团队那里得到更多的代码,所以我要说对于几乎所有的软件都足够好了。

我见过的一个嵌入式应用程序实际上,诚实到意大利面怪兽需要防止溢出,他们通过证明溢出不可能在每一行中看起来像是可能发生的溢出。

+3

下溢可能同样很糟糕,这可能是'a *(b/c)'给你的。一般应该用'((a * b).toLong/c).toInt'或者用'Double'做等价。 – 2012-03-26 17:05:18

+0

与溢出一样,处理下溢的方法是让程序员知道可能性,并使用他或她的判断。每次进行数学运算时,扩大你的论点并缩小结果对性能和代码可读性都有坏处,并且在最终结果超出或下溢时不起作用。 – mjfgates 2012-03-27 02:29:36

+0

取决于代码的性能至关重要。非常低的性能可以通过任意精度的整数获得。体面的表现可以通过扩大/缩小来获得。高性能需要程序员理解什么允许的值是如此扩大/缩小是不需要的。如果你能以某种方式完全避免分割,超高性能将是最好的。使用'Long'的 – 2012-03-27 02:59:41

6

如果你使用斯卡拉(基于我假设你是标签),一个非常通用的解决方案是写你的代码库对scala.math.Integral型类:

def naturals[A](implicit f: Integral[A]) = 
    Stream.iterate(f.one)(f.plus(_, f.one)) 

你也可以使用情境边界和Integral.Implicits为更好的语法:

import scala.math.Integral.Implicits._ 

def squares[A: Integral] = naturals.map(n => n * n) 

现在你可以使用这些方法要么IntLongBigInt根据需要,自实例为所有这些存在:

scala> squares[Int].take(10).toList 
res0: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

scala> squares[Long].take(10).toList 
res0: List[Long] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

scala> squares[BigInt].take(10).toList 
res1: List[BigInt] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 

无需更改库代码:只要使用LongBigInt,其中溢出是一种关心和Int否则。

您将在表现方面付出一些代价,但通用性和推迟IntBigInt决定的能力可能是值得的。

+0

我们再次遇到溢出问题。 – Jus12 2014-09-09 19:36:32

3

除了简单的正念之外,正如@mjfgates所指出的那样,在处理缩放十进制(非浮点)实数量时,我总是使用一些做法。这可能不适合您的特定应用程序 - 如果没有提前道歉。

首先,如果有多个度量单位在使用中,则值必须始终明确标识它们是什么。这可以通过命名约定,或者通过对每个度量单位使用单独的类。我一直使用名称 - 每个变量名都有一个后缀。除了消除errors from confusion over the units之外,它还鼓励对溢出的思考,因为这些措施不太可能被认为只是数字。其次,我最常见的溢出问题的来源通常是重新缩放 - 从一种度量转换为另一种度量 - 当它需要大量有效数字时。例如,从厘米到英寸的换算系数是0.393700787402。为了避免溢出和丢失有效数字,您需要小心按照正确的顺序进行乘法和除法。我没有在很长一段时间这样做,但我相信你想要的东西是一样的东西:

添加到Rational.scala,从书:

def rescale(i:Int) : Int = { 
     (i * (numer/denom)) + (i/denom * (numer % denom)) 

然后你得到的结果(从specs2缩短测试):

val InchesToCm = new Rational(1000000000,393700787) 
    InchesToCm.rescale(393700787) must_== 1000000000 
    InchesToCm.rescale(1) must_== 2 

这不轮,或处理负比例因子。 生产实施可能需要分解出numer/denomnumer % denom

+0

+1用于将度量单位与变量相关联。如果我试图将“字符数”和“字节数”混合在一起,那么在我应付大量可怕的DBCS到Unicode转换时,我的代码就会大声地保存我的培根。 – mjfgates 2012-03-27 02:32:57