当前位置: 首页 > 知识库问答 >
问题:

“银行家四舍五入”真的在数字上更稳定吗?

沈德寿
2023-03-14

我是说银行家的四舍五入

  1. “圆到最近,连到偶数”

当然,浮点数是用二进制尾数表示的,但我认为上面的推理仍然适用。请注意,对于IEEE754双精度,整数和intergers-plus-half都可以精确地表示绝对值(2^52),因此这些精确值实际上会在实践中显示出来。

那么方法1怎样更好呢?

共有1个答案

阮华美
2023-03-14

是的!它在数值上确实更稳定。

对于您所看到的数字[0.0,0.1,...,0.9],请注意,在round-ties-to-away下,这些数字中只有四个向下舍入(0.10.4),五个向上舍入,一个(0.0)通过舍入操作没有改变,然后这种模式1.01.92.02.9等中重复。但是在平局的情况下,我们会得到:

    [0.0,0.9]中的
  • 五个向下舍入和四个向上舍入 [1.0,1.9]
  • 中的
  • 四个向下舍入和五个向上舍入

诸如此类。平均而言,向上舍入和向下舍入得到的值数量相同。更重要的是,由舍入引入的预期误差(在输入分布的适当假设下)更接近于零。

下面是一个使用Python的快速演示。为了避免由于Python 2/Python 3在builtinround函数中的差异而产生的困难,我们给出了两个与Python版本无关的舍入函数:

def round_ties_to_even(x):
    """
    Round a float x to the nearest integer, rounding ties to even.
    """
    if x < 0:
        return -round_ties_to_even(-x)  # use symmetry
    int_part, frac_part = divmod(x, 1)
    return int(int_part) + (
        frac_part > 0.5
        or (frac_part == 0.5 and int_part % 2.0 == 1.0))

def round_ties_away_from_zero(x):
    """
    Round a float x to the nearest integer, rounding ties away from zero.
    """
    if x < 0:
        return -round_ties_away_from_zero(-x)  # use symmetry
    int_part, frac_part = divmod(x, 1)
    return int(int_part) + (frac_part >= 0.5)

现在我们来看看对[50.0,100.0]范围内的点后一位十进制值应用这两个函数所带来的平均误差:

>>> test_values = [n / 10.0 for n in range(500, 1001)]
>>> errors_even = [round_ties_to_even(value) - value for value in test_values]
>>> errors_away = [round_ties_away_from_zero(value) - value for value in test_values]
>>> import statistics
>>> statistics.mean(errors_even), statistics.stdev(errors_even)
(0.0, 0.2915475947422656)
>>> statistics.mean(errors_away), statistics.stdev(errors_away)
(0.0499001996007984, 0.28723681870533313)

这里有一个半现实的例子,演示了在数值算法中从零开始的圆环的偏差。我们将使用两两求和算法,计算浮点数列表的和。该算法将要计算的和分成两个大致相等的部分,递归地将这两个部分求和,然后将结果相加。它比单纯求和更精确,但通常不如卡汉求和等更复杂的算法。这是numpy的sum函数使用的算法。下面是一个简单的Python实现。

import operator

def pairwise_sum(xs, i, j, add=operator.add):
    """
    Return the sum of floats xs[i:j] (0 <= i <= j <= len(xs)),
    using pairwise summation.
    """
    count = j - i
    if count >= 2:
        k = (i + j) // 2
        return add(pairwise_sum(xs, i, k, add),
                   pairwise_sum(xs, k, j, add))
    elif count == 1:
        return xs[i]
    else:  # count == 0
        return 0.0

我们在上面的函数中包含了一个参数add,表示用于添加的操作。默认情况下,它使用Python的普通加法算法,在典型的机器上,该算法将解析为标准的IEEE 754加法,使用舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入-舍入模式。

我们希望查看pairwise_sum函数的预期错误,它同时使用标准加法和从零开始的加法版本。我们的第一个问题是,我们没有一个简单、可移植的方法来从Python中改变硬件的舍入模式,二进制浮点的软件实现会很大而且很慢。幸运的是,我们可以使用一个技巧,在仍然使用硬件浮点的同时,将圆环从零中取出来。对于该技巧的第一部分,我们可以使用Knuth的“2sum”算法将两个浮点数相加,并获得正确的舍入和以及该和中的精确误差:

def exact_add(a, b):
    """
    Add floats a and b, giving a correctly rounded sum and exact error.

    Mathematically, a + b is exactly equal to sum + error.
    """
    # This is Knuth's 2Sum algorithm. See section 4.3.2 of the Handbook
    # of Floating-Point Arithmetic for exposition and proof.
    sum = a + b
    bv = sum - a
    error = (a - (sum - bv)) + (b - bv)
    return sum, error
def add_ties_away(a, b):
    """
    Return the sum of a and b. Ties are rounded away from zero.
    """
    sum, error = exact_add(a, b)
    sum2, error2 = exact_add(sum, 2.0*error)
    if error2 or not error:
        # Not a tie.
        return sum
    else:
        # Tie. Choose the larger of sum and sum2 in absolute value.
        return max([sum, sum2], key=abs)

现在我们可以比较结果了。sample_sum_errors是一个函数,它生成范围[1,2]内的浮点数列表,使用普通的圆连加法和自定义的圆连离零版本将它们相加,与精确的和进行比较,并返回这两个版本的错误,以最后一个位置的单位度量。

import fractions
import random

def sample_sum_errors(sample_size=1024):
    """
    Generate `sample_size` floats in the range [1.0, 2.0], sum
    using both addition methods, and return the two errors in ulps.
    """
    xs = [random.uniform(1.0, 2.0) for _ in range(sample_size)]
    to_even_sum = pairwise_sum(xs, 0, len(xs))
    to_away_sum = pairwise_sum(xs, 0, len(xs), add=add_ties_away)

    # Assuming IEEE 754, each value in xs becomes an integer when
    # scaled by 2**52; use this to compute an exact sum as a Fraction.
    common_denominator = 2**52
    exact_sum = fractions.Fraction(
        sum(int(m*common_denominator) for m in xs),
        common_denominator)

    # Result will be in [1024, 2048]; 1 ulp in this range is 2**-44.
    ulp = 2**-44
    to_even_error = (fractions.Fraction(to_even_sum) - exact_sum) / ulp
    to_away_error = (fractions.Fraction(to_away_sum) - exact_sum) / ulp

    return to_even_error, to_away_error

下面是一个示例运行:

>>> sample_sum_errors()
(1.6015625, 9.6015625)

因此,使用标准加法的误差为1.6ulps,当从零四舍五入时,误差为9.6ulps。当然,从零开始的方法看起来更糟糕,但一次运行并不特别令人信服。让我们这样做10000次,每次使用不同的随机样本,并绘制我们得到的误差。代码如下:

import statistics
import numpy as np
import matplotlib.pyplot as plt

def show_error_distributions():
    errors = [sample_sum_errors() for _ in range(10000)]
    to_even_errors, to_away_errors = zip(*errors)
    print("Errors from ties-to-even: "
          "mean {:.2f} ulps, stdev {:.2f} ulps".format(
              statistics.mean(to_even_errors),
              statistics.stdev(to_even_errors)))
    print("Errors from ties-away-from-zero: "
          "mean {:.2f} ulps, stdev {:.2f} ulps".format(
              statistics.mean(to_away_errors),
              statistics.stdev(to_away_errors)))

    ax1 = plt.subplot(2, 1, 1)
    plt.hist(to_even_errors, bins=np.arange(-7, 17, 0.5))
    ax2 = plt.subplot(2, 1, 2)
    plt.hist(to_away_errors, bins=np.arange(-7, 17, 0.5))
    ax1.set_title("Errors from ties-to-even (ulps)")
    ax2.set_title("Errors from ties-away-from-zero (ulps)")
    ax1.xaxis.set_visible(False)
    plt.show()
Errors from ties-to-even: mean 0.00 ulps, stdev 1.81 ulps
Errors from ties-away-from-zero: mean 9.76 ulps, stdev 1.40 ulps

我计划更进一步,对两个样本进行偏差的统计测试,但从零开始的联系方法的偏差是如此显著,以至于看起来没有必要。有趣的是,虽然从零开始的方法给出的结果较差,但它确实给出了较小的误差传播。

 类似资料:
  • 问题 你想对浮点数执行指定精度的舍入运算。 解决方案 对于简单的舍入运算,使用内置的 round(value, ndigits) 函数即可。比如: >>> round(1.23, 1) 1.2 >>> round(1.27, 1) 1.3 >>> round(-1.27, 1) -1.3 >>> round(1.25361,3) 1.254 >>> 当一个值刚好在两个边界的中间的时候, round

  • 问题内容: 如何在Java中四舍五入到特定的倍数?在excel中,该函数可以轻松舍入到指定的倍数,如下所示: 所以将返回,如果和如果。 到目前为止,我发现的所有舍入函数都始终舍入到最接近的整数或指定的小数位数,但是我希望能够为每个变量更改倍数。有谁知道哪种功能最适合这种情况? 问题答案: 只需除以数字,四舍五入,然后乘以数字即可。

  • 有人能告诉我怎么打双人球吗 e、 g.从双倍值55.6666666666667开始-四舍五入到双倍值56.0000000000- 或者从55.333333333333开始-四舍五入到55.0000000000的两倍- 谢谢。

  • 本节讨论了精度数学的四舍五入特性,ROUND()函数,以及插入DECIMAL列时的四舍五入特性。 ROUND()函数的行为取决于其参量是准确的还是近似的: ·对于准确值数值,ROUND()采用“半值向上舍入”规则:如果小数部分的值为.5或更大,如果是正数,向上取下一个整数,如果是负数,向下取下一个整数(换句话讲,以0为界限执行舍入)。如果小数部分的值小于.5,如果是正数,向下取下一个整数,如果是负

  • 问题内容: 我怎么总是将a舍入为一个,而不舍入它。我知道,但是我希望它总是四舍五入。因此,如果为,则四舍五入为4。 问题答案: 您可以使用方法。 请参阅JavaDoc链接:https ://docs.oracle.com/javase/10/docs/api/java/lang/Math.html#ceil(double ) 从文档: 细胞 返回大于或等于自变量且等于数学整数的最小(最接近负无穷大