【问题标题】:Scala Int overflowScala Int 溢出
【发布时间】:2018-04-23 05:57:45
【问题描述】:

谁能帮我理解为什么 Int 类型溢出结果与更高的数字不一致。有时为正,有时为负,最后收敛为零。

import scala.annotation.tailrec

object FactorialExample {

  def main(args: Array[String]): Unit = {
    (1 to 100).foreach(i => println(s"Factorial of ${i} is " + factorial(i)))
  }

  def factorial(i: Int): Int = {
    @tailrec
    def fact(i: Int, acc: Int): Int = {
      if (i <= 0) acc
      else fact(i - 1, acc * i)
    }
    fact(i, 1)
  }
}

【问题讨论】:

  • Intsigned 32bit number 所以它是 cannot store 100!
  • 我确实理解它的溢出情况,但我的问题是一旦溢出情况进入图片编译器应该抛出一个数字,用户应该知道它的垃圾并需要使用其他数据类型。
  • 其实整数溢出并不是垃圾,是一个在安全中被大量使用的特性。 stackoverflow.com/questions/2913618/…
  • “我明白它的溢出情况”完美。 “我的问题是一旦溢出情况进入图片编译器应该抛出一个数字”这不是一个问题,这是你的意见,它没有多大意义(“抛出一个数字”是什么意思?)如果你想问为什么整数溢出不会导致异常,请问。

标签: scala


【解决方案1】:

正如其他人指出的那样,ScalaJava 默认情况下不对整数值执行溢出检查。 (我同意悄悄地溢出一个值会导致意想不到的——和不想要的——行为,以及许多非常微妙的错误。)然而,一切都没有丢失。 java.lang.Math 具有静态方法(addExactincrementExactmultiplyExactsubtractExact 等),如果计算溢出基础类型,则会抛出 ArithmeticException。例如:

scala> def factorial(n: Int): Int = {
     |   if(n <= 1) 1
     |   else Math.multiplyExact(n, factorial(n - 1))
     | }
factorial: (n: Int)Int

scala> factorial(12)
res0: Int = 479001600

scala> factorial(13)
java.lang.ArithmeticException: integer overflow
  at java.lang.Math.multiplyExact(Math.java:867)
  at .factorial(<console>:13)
  ... 28 elided

这个版本不是尾递归的,抛出异常违反函数式编程原则(更好的方法是返回一个封装了可能的错误的类型,例如Try[Int]Either[Throwable, Int]) ,但它会检测并报告Int 类型的溢出。详情请参阅java.lang.Math

例如,这是一个不会耗尽堆栈的功能版本:

scala> import scala.annotation.tailrec
import scala.annotation.tailrec

scala> import scala.util.Try
import scala.util.Try

scala> def factorial(n: Int): Try[Int] = {
     |   @tailrec
     |   def fact(i: Int, acc: Int): Int = {
     |     if(i <= 1) acc
     |     else fact(i - 1, Math.multiplyExact(i, acc))
     |   }
     |   Try(fact(n, 1))
     | }

scala> factorial(12)
res0: scala.util.Try[Int] = Success(479001600)

scala> factorial(13)
res1: scala.util.Try[Int] = Failure(java.lang.ArithmeticException: integer overflow)

关于溢出的值:你可以认为这样的结果是有效的随机的。这是只保留溢出结果的最右边 32 位(Int 类型的容量)的结果。如果设置了最高有效位,则该值将被视为负值。 (正如其他人所指出的,有关如何解释 Int 位模式的更详细说明,请参阅 Two's Complement。)虽然这取决于您正在执行的计算,但结果不会收敛 一般情况下为零。

例如,factorial(13) 是导致Int 溢出的最小参数值。如果不使用Math.multiplyExact,您的原始函数将返回值1932053504——不幸的是,这显然是错误的。如果我们使用Long 而不是Int,我们可以完全按照6227020800 计算这个结果。但是让我们看看这些数字的二进制表示:

scala> 1932053504.toBinaryString
res0: String = 1110011001010001100110000000000

scala> 6227020800L.toBinaryString
res1: String = 101110011001010001100110000000000

最右边的 32 位具有相同的值! (请注意,省略了前导零。)但是,Long 表示有一个额外的位,它不能存储在 Int 表示中,这会产生很大的不同。不幸的是,递归算法 (n! = n * (n - 1)!) 意味着一旦出现溢出,这些错误就会复合,这意味着,对于高于 13 的参数值,我们开始乘以溢出的值,从而导致总垃圾,并进一步破坏溢出的值。特别是,一旦阶乘计算为零,所有后续值显然也将为零。

【讨论】:

  • 好答案。你显然比我写我的时候更有耐心:-)
  • @Rich。谢谢!但有时更简洁的答案可以解决问题...... ;-)
【解决方案2】:

为什么 Int 类型溢出结果与更高的数字不一致。有时为正,有时为负,最后收敛为零。

这就是溢出的 int 类型的行为方式。您可以阅读更多内容,例如Modular arithmetic Two's Complement。您有具体问题吗?

我确实理解它的溢出情况,但我的问题是一旦溢出情况进入图片编译器应该抛出一个数字,用户应该知道它的垃圾并且需要使用其他数据类型

回复Why, In Java arithmetic, overflow or underflow will never throw an Exception?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-03
    • 2016-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多