SPOJ`CUBERT`

时间:2016-02-07 14:15:45

标签: python python-3.x math floating-point precision

对于我在SPOJ上this problem的解决方案,我得到错误答案

问题要求计算整数的多维数据集根(最长可达150位),并输出截断的答案,最多10位小数。
它还要求将答案模10中所有数字的总和计算为“校验和”。值。

以下是确切的问题陈述:

  

您的任务是计算给定正整数的立方根。   我们不记得为什么我们需要这个,但它有一些东西   与公主,年轻的农民,亲吻和一半的王国共同   (一个巨大的,我们可以向你保证)。

     

编写一个程序来解决这个关键任务。

     

输入

     

输入以包含单个整数t <= 20的行开头   测试用例数量。 t测试用例如下。

     

下一行包含最多150个十进制的大正整数   数字。每个数字都在输入文件的单独行中。该   输入文件可能包含空行。数字可以在或之前   后跟空格,但没有行超过255个字符。

     

输出

     

对于输入文件中的每个数字,程序应输出一行   由两个由单个空格分隔的值组成。第二个值   是给定数字的立方根,截断后(不是圆形!)   小数点后十位。第一个值是所有打印的校验和   立方根的数字,计算为打印数字的总和   模10。

     

示例

     

输入
  5
  1

     

8

     

1000

     

2 33076161

     

输出
  1 1.0000000000
  2 2.0000000000
  1 10.0000000000
  0 1.2599210498
  6 321.0000000000

这是我的解决方案:

from math import pow


def foo(num):
    num_cube_root = pow(num, 1.0 / 3)
    # First round upto 11 decimal places
    num_cube_root = "%.11f" % (num_cube_root)
    # Then remove the last decimal digit
    # to achieve a truncation of 10 decimal places
    num_cube_root = str(num_cube_root)[0:-1]

    num_cube_root_sum = 0
    for digit in num_cube_root:
        if digit != '.':
            num_cube_root_sum += int(digit)
    num_cube_root_sum %= 10

    return (num_cube_root_sum, num_cube_root)


def main():
    # Number of test cases
    t = int(input())
    while t:
        t -= 1
        num = input().strip()
        # If line empty, ignore
        if not num:
            t += 1
            continue

        num = int(num)
        ans = foo(num)
        print(str(ans[0]) + " " + ans[1])


if __name__ == '__main__':
    main()

它适用于示例案例:Live demo

有人能说出这个解决方案有什么问题吗?

2 个答案:

答案 0 :(得分:8)

您的解决方案有两个问题,都与使用浮点运算有关。第一个问题是Python float只携带大约16位有效精度的十进制数字,所以一旦你的答案需要超过16位有效数字左右(所以在点之前超过6位,之后是10位数) ),你很少有希望获得正确的尾随数字。第二个问题更微妙,甚至影响n的小值。这就是你的四舍五入到十进制数后然后丢弃最后一位的方法会因double rounding而导致潜在错误。例如,取n = 33n的立方根,大约20位小数,是:

3.20753432999582648755...

如果在该点之后四舍五入到11个位置,则最终得到

3.20753433000

现在丢弃最后一位数字给出3.2075343300,这不是你想要的。问题是,小数到11位小数可能会影响到第11位数左边的数字。

那么你能做些什么来解决这个问题呢?好吧,你可以完全避免浮点数并将其减少为纯整数问题。我们需要一些整数n的立方根到10个小数位(将最后一个位置向下舍入)。这相当于将10**30 * n的立方根计算为最接近的整数,再次向下舍入,然后将结果除以10**10。因此,这里的基本任务是计算任何给定整数n的立方根的底限。我无法找到有关计算整数多维数据集根目录的任何现有Stack Overflow答案(在Python中更少),所以我认为值得详细说明如何这样做。

计算整数的整数根证明非常简单(借助一点点数学)。存在各种可能的方法,但是一种既有效又易于实现的方法是使用Newton-Raphson方法的纯整数版本。在实数上,牛顿求解方程x**3 = n的方法对x的立方根取近似n,并迭代以返回改进的近似。所需的迭代是:

x_next = (2*x + n/x**2)/3

在实际情况中,您将重复迭代,直到达到某个所需的容差。事实证明,在整数上,基本上相同的迭代起作用,并且在正确的退出条件下,它将给我们完全正确的答案(不需要容差)。整数情况下的迭代是:

a_next = (2*a + n//a**2)//3

(请注意使用底线除法运算符//代替上面通常的真正除法运算符/。)数学上,a_next正是(2*a + n/a**2)/3的最低限度

这里有一些基于此迭代的代码:

def icbrt_v1(n, initial_guess=None):
    """
    Given a positive integer n, find the floor of the cube root of n.

    Args:
        n : positive integer
        initial_guess : positive integer, optional. If given, this is an
            initial guess for the floor of the cube root. It must be greater
            than or equal to floor(cube_root(n)).

    Returns:
        The floor of the cube root of n, as an integer.
    """
    a = initial_guess if initial_guess is not None else n
    while True:
        d = n//a**2
        if a <= d:
            return a
        a = (2*a + d)//3

一些例子使用:

>>> icbrt_v1(100)
4
>>> icbrt_v1(1000000000)
1000
>>> large_int = 31415926535897932384626433
>>> icbrt_v1(large_int**3)
31415926535897932384626433
>>> icbrt_v1(large_int**3-1)
31415926535897932384626432

我们很快就会解决icbrt_v1中的一些烦恼和效率低下问题。但首先,简要说明上述代码的工作原理。请注意,我们从一个初始猜测开始,假设它大于或等于立方根的底面。我们将展示此属性是循环不变量:每次时间我们到达while循环的顶部,a至少为floor(cbrt(n))。此外,每次迭代产生的a值严格小于旧值,因此我们的迭代最终会收敛到floor(cbrt(n))。为了证明这些事实,请注意,当我们进入while循环时,有两种可能性:

案例1. a严格大于n的多维数据集根。然后a > n//a**2,代码进入下一次迭代。写a_next = (2*a + n//a**2)//3,然后我们有:

  • a_next >= floor(cbrt(n))。这是因为(2*a + n/a**2)/3至少是n的立方根,而a又应用于an/a**2和{ {1}}:这三个量的几何平均值恰好是n的立方根,因此算术平均值必须至少 n的立方根。因此,我们的循环不变量将保留用于下一次迭代。

  • a_next < a:因为我们假设a大于多维数据集根n/a**2 < a,因此(2a + n/a**2) / 3 小于a,因此floor((2a + n/a**2) / 3) < a。这保证了我们在每次迭代时都能在解决方案上取得进展。

案例2. a小于或等于n的立方根。然后是a <= floor(cbrt(n)),但是从上面建立的循环不变量我们也知道a >= floor(cbrt(n))。我们已经完成了:a是我们追求的价值。此时,while循环退出,因为a <= n // a**2

上面的代码存在一些问题。首先,从n的初始猜测开始是低效的:代码将花费其前几次迭代(大致)每次将a的当前值除以3,直到它进入解决方案的邻域。对于初始猜测(以及在Python中可以轻松计算的一个)的更好选择是使用超过n的立方根的2的第一个幂。

initial_guess = 1 << -(-n.bit_length() // 3)

更好的是,如果n足够小以避免溢出,则使用浮点算法来提供初始猜测,例如:

initial_guess = int(round(n ** (1/3.)))

但这引出了我们的第二个问题:我们的算法的正确性要求初始猜测不小于实际的整数立方根,并且随着n变大,我们无法保证基于浮动的initial_guess以上(虽然足够小n,我们可以)。幸运的是,这是一个非常简单的修复:对于任何正整数a,如果我们执行单次迭代,我们总是得到一个至少{{{的值。 1}}(使用我们上面使用的相同的AM-GM参数)。因此,我们所要做的就是在开始测试收敛之前至少执行一次迭代。

考虑到这一点,这里是上述代码的更高效版本:

floor(cbrt(a))

随着def icbrt(n): """ Given a positive integer n, find the floor of the cube root of n. Args: n : positive integer Returns: The floor of the cube root of n, as an integer. """ if n.bit_length() < 1024: # float(n) safe from overflow a = int(round(n**(1/3.))) a = (2*a + n//a**2)//3 # Ensure a >= floor(cbrt(n)). else: a = 1 << -(-n.bit_length()//3) while True: d = n//a**2 if a <= d: return a a = (2*a + d)//3 的掌握,将所有内容放在一起计算多维数据集根到十个小数位是很容易的。这里,为简单起见,我将结果输出为字符串,但您可以轻松构造icbrt实例。

Decimal

示例输出:

def cbrt_to_ten_places(n):
    """
    Compute the cube root of `n`, truncated to ten decimal places.

    Returns the answer as a string.
    """
    a = icbrt(n * 10**30)
    q, r = divmod(a, 10**10)
    return "{}.{:010d}".format(q, r)

答案 1 :(得分:2)

您可以尝试使用具有足够大精度值的decimal模块。

编辑:感谢@DSM,我意识到decimal模块不会产生非常精确的立方根。我建议您检查是否所有数字都是9,如果是这种情况,请将其四舍五入为整数。

此外,我现在也使用Decimals执行1/3除法,因为将1/3的结果传递给Decimal构造函数会导致精度降低。

import decimal

def cbrt(n):
    nd = decimal.Decimal(n)
    with decimal.localcontext() as ctx:
        ctx.prec = 50
        i = nd ** (decimal.Decimal(1) / decimal.Decimal(3))
    return i

ret = str(cbrt(1233412412430519230351035712112421123121111))
print(ret)
left, right = ret.split('.')
print(left + '.' + ''.join(right[:10]))

输出:

107243119477324.80328931501744819161741924145124146
107243119477324.8032893150

cbrt(10)的输出是:

9.9999999999999999999999999999999999999999999999998