常见的做法如何处理Integer溢出?

时间:2012-03-26 15:28:46

标签: scala biginteger integer-overflow

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

可以强制要求BigInt并禁止使用Integer,但这是好主意还是坏主意?

4 个答案:

答案 0 :(得分: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
}

您可以使用rich-your-library模式将其转换为运算符;如果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的现有方法时,它将无法帮助您。

答案 1 :(得分:6)

如果您正在使用Scala(并且基于我假设您的标记),一个非常通用的解决方案是针对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一起使用,因为Integral的所有实例都存在:

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

您将在效果方面支付一些罚金,但推迟Int - 或 - BigInt决定的通用性和能力可能是值得的。

答案 2 :(得分:5)

到目前为止,关于整数溢出的大多数常见实践是程序员应该知道问题存在,要注意它们可能发生的情况,并进行适当的检查或重新排列数学这样就不会发生溢出,例如做*(b / c)而不是(a * b)/ c。如果项目使用单元测试,它们将包括尝试强制溢出的情况。

我从未参与或看过需要更多内容的团队代码,因此我会说这对几乎所有软件都足够好。

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

答案 3 :(得分:4)

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

首先,如果使用多个度量单位,则值必须始终清楚地标识它们是什么。这可以通过命名约定,或者为每个度量单位使用单独的类。我总是只使用名称 - 每个变量名称的后缀。除了消除errors from confusion over the units之外,它还鼓励考虑溢出,因为这些措施不太可能被视为数字。

其次,我最常见的溢出问题源通常是重新缩放 - 从一个度量转换为另一个度量 - 当它需要大量有效数字时。例如,从cm到英寸的转换因子为0.393700787402。为了避免溢出和丢失有效数字,您需要注意以正确的顺序乘以和除。我很久没有这样做了,但我相信你想要的是:

从The Book:

添加到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