大数的Scala阶乘有时会崩溃,有时则不会崩溃

时间:2009-07-27 10:17:16

标签: scala jvm

以下程序经过编译和测试,有时会返回结果,有时会用

填充屏幕
java.lang.StackOverflowError
at scala.BigInt$.apply(BigInt.scala:47)
at scala.BigInt.equals(BigInt.scala:129)
at scala.runtime.BoxesRunTime.equals(Unknown Source)
at bigint$.factorial(fact2.scala:3)
at bigint$.factorial(fact2.scala:3)
...

该计划:

object bigint extends Application {
  def factorial(n: BigInt): BigInt = if (n == 0) 1 else n * factorial(n-1)
  println("4391! = "+factorial(4391))
}

我的问题:

  • 是因为JVM上存在堆栈溢出,有时会发生,有时却不会?
  • 这种不确定行为是否被视为错误?
  • 我认为Scala没有尾递归这个?我怎样才能让它尾随这个呢?

详细说明:

  

Scala编译器版本2.7.5.final -   版权所有2002-2009,LAMP / EPFL Scala   代码运行器版本2.7.5.final -   版权所有2002-2009,LAMP / EPFL

     

java版“1.6.0_0”OpenJDK   运行时环境(构建   1.6.0_0-b11)OpenJDK客户端虚拟机(版本1.6.0_0-b11,混合模式,共享)

     

Ubuntu 2.6.24-24-generic

3 个答案:

答案 0 :(得分:13)

如果递归调用是函数中的最后一个语句,则尾调用优化仅适用于Scala。这是非常有限的。 Scala的书说:

  

[...]尾调用优化是   仅限于以下情况:   方法或嵌套函数调用自身   直接作为其最后一次操作,   没有通过函数值   或其他一些中间人。

在你的情况下,递归调用是一个更大的表达式的一部分,并且它本身不是最后一个操作 - 这里的最后一个操作是乘法。

This article演示了如何使其发挥作用:

class Factorial {
  def factorial(n: Int): Int = {
    def factorialAcc(acc: Int, n: Int): Int = {
      if (n <= 1) acc
      else factorialAcc(n * acc, n - 1)
    }
    factorialAcc(1, n)
  }
}

答案 1 :(得分:7)

在Scala 2.8中,当您希望使用尾调用优化时,可以使用@tailrec注释,如果编译器无法执行此操作,则会收到警告。

答案 2 :(得分:1)

如果你真的有大数字,那么有许多approximations,例如Scala中的这个使用素数分解:

class SwingFactorial(n: Int) {

  def name() = "SwingFactorial"

  def value(): BigInt =
    {
      if (n < 0)
         {
          throw new IllegalArgumentException(
          "Factorial: n has to be >= 0, but was " + n)
         }

      ndiv2OddFact = BigInt(1)
      ndiv4OddFact = ndiv2OddFact

      return oddFactorial(n) << (n - MathFun.bitCount(n))
    }

  private def oddFactorial(n: Int): BigInt =
    {
      val oddFact =
      if (n < Small.oddFactorial.length)
        {
          BigInt(Small.oddFactorial(n))
        }
      else
        {
          val of = oddFactorial(n / 2)
          (of * of) * oddSwing(n)
        }

      ndiv4OddFact = ndiv2OddFact
      ndiv2OddFact = oddFact
      return oddFact
    }

  private def oddSwing(n: Int): BigInt =
    {
      if (n < Small.oddSwing.length)
      {
        return BigInt(Small.oddSwing(n))
      }

      val len = if ((n % 4) != 2) (n - 1) / 4 + 1 else (n - 1) / 4
      val high = n - ((n + 1) & 1)
      val ndiv4 = n / 4
      val oddFact = if (ndiv4 < Small.oddFactorial.length)
            BigInt(Small.oddFactorial(ndiv4)) else ndiv4OddFact

      return product(high, len) / oddFact
    }

    private def product(m: Int, len: Int): BigInt =
    {
      if (len == 1) return BigInt(m) 
      if (len == 2) {val M = m.toLong; return BigInt(M * (M - 2))}

      val hlen = len >>> 1
      return product(m - hlen * 2, len - hlen) * product(m, hlen)
    }

  private var ndiv4OddFact = BigInt(1)
  private var ndiv2OddFact = BigInt(1)
} 

用法:

var fs = new SwingFactorial(n)
val a = fs.value()