【问题标题】:SCALA: Function for Square root of BigIntSCALA:BigInt 的平方根函数
【发布时间】:2018-07-06 15:05:33
【问题描述】:

我在互联网上搜索了一个函数来使用 scala 编程语言找到 BigInt 的精确平方根。我没有得到一个,但是看到了一个 Java 程序,我将该函数转换为 Scala 版本。它正在工作,但我不确定它是否可以处理非常大的 BigInt。但它只返回 BigInt。不是 BigDecimal 作为平方根。它表明在代码中进行了一些位操作,对数字进行了一些硬编码,例如shiftRight(5), BigInt("8") and shiftRight(1)。我可以清楚地理解逻辑,但不是这些位移数字和数字 8 的硬编码。可能是这些位移函数在 scala 中不可用,这就是为什么需要在少数地方转换为 java BigInteger 的原因。这些硬编码的数字可能会影响结果的精度。我只是将 java 代码更改为 scala 代码,只是复制了确切的算法。这是我用 scala 编写的代码:

   def sqt(n:BigInt):BigInt = {
      var a = BigInt(1)
      var b = (n>>5)+BigInt(8)
      while((b-a) >= 0) {
          var mid:BigInt = (a+b)>>1
          if(mid*mid-n> 0) b = mid-1
          else a = mid+1
         }
      a-1
   }

我的积分是:

  1. 我们不能返回 BigDecimal 而不是 BigInt 吗?我们该怎么做?
  2. 这些硬编码的数字shiftRight(5), shiftRight(1) and 8 是如何关联的 以精确到结果。

我在 scala REPL 中测试了一个数字:函数sqt 给出了平方数的精确平方根。但不是如下的实际数字:

scala> sqt(BigInt("19928937494873929279191794189"))
res9: BigInt = 141169888768369

scala> res9*res9
res10: scala.math.BigInt = 19928937494873675935734920161

scala> sqt(res10)
res11: BigInt = 141169888768369

scala>

我理解shiftRight(5) means divide by 2^5 ie.by 32 in decimal等等..但是为什么移位操作后在这里添加8?为什么正好是 5 个班次?作为第一个猜测?

【问题讨论】:

    标签: scala square-root


    【解决方案1】:

    您的问题 1 和问题 3 实际上是同一个问题。

    1. 这些位移如何 [如何] 影响 [the] 结果的精度?

    他们没有。

    1. 这些硬编码的数字...与结果的精度有何关系?

    他们不是。

    有许多不同的方法/算法可用于估计/计算数字的平方根(如 here 所示)。您发布的算法似乎是一个非常简单的二进制搜索。

    1. 选择一个保证小于目标的数字an 的平方根)。
    2. 选择一个保证大于目标的数字bn 的平方根)。
    3. 计算mid,即ab 之间的整数中点。
    4. 如果mid 大于(或等于)目标,则将b 移动到mid(-1,因为我们知道它太大了)。
    5. 如果mid 小于目标,则将a 移动到mid(+1,因为我们知道它太小了)。
    6. 重复 3,4,5 直到 a 不再小于 b
    7. a-1 返回为n 的平方根向下舍入为整数

    位移位和硬编码数字用于选择b 的初始值。但b 只大于目标。我们本可以完成var b = n。为什么这么麻烦?

    一切都与效率有关。 b 离目标越近,找到结果所需的迭代次数就越少。为什么在班次后加8?因为 31>>5 为零,不大于目标。作者选择了(n>>5)+8,但他/她可能选择了(n>>7)+12。需要权衡取舍。

    1. 我们不能返回 BigDecimal 而不是 BigInt 吗?我们该怎么做?

    这是一种方法。

    def sqt(n:BigInt) :BigDecimal = {
      val d = BigDecimal(n)
      var a = BigDecimal(1.0)
      var b = d
      while(b-a >= 0) {
        val mid = (a+b)/2
        if (mid*mid-d > 0) b = mid-0.0001  //adjust down
        else               a = mid+0.0001  //adjust up
      }
      b
    }
    

    有更好的算法来计算浮点平方根值。在这种情况下,您可以通过使用较小的调整值获得更好的精度,但效率会变得更差。

    【讨论】:

    • 很好的解释 jwvh。我们可以将所谓的hardcoded values 5(shifts) and 8 in terms variables based on the size of n 传递给函数吗?或者你能建议非常大的数字的最佳算法吗?
    • 一个“很好的解释”但仍然不值得投票? (哇。难受的听众。)您的后续问题很有趣,但不再特定于 Scala 代码。你可能想把它带到group that specializes in this sort of thing
    【解决方案2】:

    我们不能返回 BigDecimal 而不是 BigInt 吗?我们该怎么做?

    如果你想要精确的根,这是没有意义的:如果BigInt 的平方根可以用BigDecimal 精确表示,它可以用BigInt 表示。如果您不想要精确的根,则需要指定精度并修改算法(在大多数情况下,Double 已经足够好,而且比BigDecimal 快得多)。

    我理解 shiftRight(5) 表示除以 2^5 即除以 32 以十进制等等..但是为什么在移位操作后在这里添加 8?为什么正好是 5 班?作为第一个猜测?

    这些不是唯一的选择。关键是对于每个正数nn/32 + 8 >= sqrt(n)(其中sqrt 是数学平方根)。这最容易通过一些微积分(或仅通过构建差异图)来显示。所以一开始我们就知道a <= sqrt(n) <= b(除非n == 0 可以单独检查),并且您可以在每个步骤中验证这一点是否正确。

    【讨论】:

    • 这一切归结为finding two closest expressions a and b in terms of n, such that a<n and b>n to reduce number of iterations. Are there any such established(proved) expressions better than n/32 +8 for b and also for a instead of taking a crude guess of 1
    • Exact 我的意思是非常非常大的 BigDecimal 数字,甚至比 Double 表示的(309 digits or so)大得多,假设 n 是一个 10000 位和 20 位的 BigDecimal小数点后。和 ` r 是它的平方根, then r^2 ( r squared` ) 与原始 n 相比,必须至少匹配十位数,however big the passed n is.. 如果这是我的要求,我应该如何选择 better algorithm 和 @算法中的987654339@。
    • 对于上下文,由于原始帖子中的每个人可能都不清楚这一点,因此任何正整数要么具有整数平方根,要么平方根是无理数。 (如果它是无理数,它不能用 BigDecimal 精确表示)。 johndcook.com/blog/2009/12/20/roots-of-integers
    猜你喜欢
    • 2015-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多