如何避免浮点数误差?

45

我试图编写一个近似平方根的函数(我知道有数学模块……但我想自己来做),但是浮点算术让我很困扰。你如何避免这种情况?

def sqrt(num):
    root = 0.0
    while root * root < num:
        root += 0.01
    return root

使用它会产生以下结果:

>>> sqrt(4)
2.0000000000000013
>>> sqrt(9)
3.00999999999998

我知道我可以使用round()函数,但我想让它更加精确。我想要计算到小数点后六七位,如果我使用四舍五入是不可能实现的。我想了解如何在Python中正确处理浮点数运算。


也许可以尝试使用 decimal 模块,该模块专为精度而设计。 - Michael0x2a
2个回答

54

这实际上与Python无关 - 任何使用硬件二进制浮点运算的语言都会出现相同的行为。请先阅读文档

在阅读完之后,您将更好地了解,在您的代码中您并没有加上0.01。你实际上是在加上这个:

>>> from decimal import Decimal
>>> Decimal(.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')

这个字符串显示了二进制浮点数的精确十进制值(在C中称为“双精度”)和精确十进制值0.01的近似值之间的差异。实际上,您添加的是略大于1/100的值。

控制浮点数数值误差是称为“数值分析”的领域,它是一个非常庞大而复杂的主题。只要您惊讶于浮点数仅是十进制值的近似值,就使用decimal模块。 这将帮助您解决许多“表层”问题。例如,对于您的函数进行如下小修改:

from decimal import Decimal as D

def sqrt(num):
    root = D(0)
    while root * root < num:
        root += D("0.01")
    return root

那么:

>>> sqrt(4)
Decimal('2.00')
>>> sqrt(9)
Decimal('3.00')

在简单的例子中,它并不真正更加精确,但可能不会有那么多惊喜,因为现在它添加了确切的百分之一。

另一种选择是继续使用浮点数,并添加一些在二进制浮点数中确实可表示的值:形如I/2**J的值。例如,可以添加0.125(1/8)或0.0625(1/16),而不是添加0.01。

然后查找“牛顿迭代法”以计算平方根 ;-)


记录一下,我已经阅读了文档,并且已经知道了使用二进制表示存储时浮点数精度的问题。我忘记了牛顿迭代法。你在这里回答了我的所有问题!当你发现SO时,是我的幸运日。我想知道Decimal模块是如何工作的。除了阅读源代码之外,还有其他方法吗? - temporary_user_name
1
嗯,decimal 最初是用 Python 编写的,它可以处理十进制数字列表(0、1、2、...、9)。非常模拟我们在纸上进行算术运算的方式!“浮点数”只需要将(十进制)指数添加到表示中,然后要非常小心;-) 当前的 decimal 模块是用 C 编写的,更加晦涩难懂 :-( - Tim Peters
1
就像你说的一样,我尝试使用decimal模块来计算4-3.2。a = Decimal(4) b = Decimal(3.2)但是a-b的结果是Decimal('0.7999999999999998223643160600')。 - Srinesh
10
请尝试使用Decimal('4') - Decimal('3.2') - Robᵩ

5

我的意思是,有像 decimalfractions 这样的模块。但是我创造了一个专门解决这些问题的类。这个类只能解决加法、减法、乘法、地板除法、除法和取模。但是它可以很容易地扩展。它基本上将浮点数转换为列表([浮点数,将浮点数乘以十的幂得到整数]),然后进行算术运算。在Python中,整数比浮点数更精确。这就是这个类的优势所在。因此,不多说了,下面是代码:

class decimal():
    # TODO: # OPTIMISE: code to maximize performance
    """
    Class decimal, a more reliable alternative to float. | v0.1
    ============================================================
            Python's floats (and in many other languages as well) are
    pretty inaccurate. While on the outside it may look like this:

    .1 + .1 + .1

            But on the inside, it gets converted to base 2. It tells
    the computer, "2 to the power of what is 0.1?". The
    computer says, "Oh, I don't know; would an approximation
    be sufficient?"
    Python be like, "Oh, sure, why not? It's not like we need to
    give it that much accuracy."
            And so that happens. But what they ARE good at is
    everything else, including multiplying a float and a
    10 together. So I abused that and made this: the decimal
    class. Us humans knows that 1 + 1 + 1 = 3. Well, most of us
    anyway but that's not important. The thing is, computers can
    too! This new replacement does the following:

            1. Find how many 10 ^ n it takes to get the number inputted
                    into a valid integer.
            2. Make a list with the original float and n (multiplying the by
                    10^-n is inaccurate)

            And that's pretty much it, if you don't count the
    adding, subtracting, etc algorithm. This is more accurate than just
    ".1 + .1 + .1". But then, it's more simple than hand-typing
    (.1 * 100 + .01 * 100 + .1 * 100)/100
    (which is basically the algorithm for this). But it does have it's costs.
    --------------------------------------------------------------------------

    BAD #1: It's slightly slower then the conventional .1 + .1 + .1 but
        it DOES make up for accuracy

    BAD #2: It's useless, there are many libraries out there that solves the
            same problem as this. They may be more or less efficient than this
            method. Thus rendering this useless.
    --------------------------------------------------------------------------
    And that's pretty much it! Thanks for stopping by to read this doc-string.
    --------------------------------------------------------------------------
        Copyright © 2020 Bryan Hu

        Permission is hereby granted, free of charge, to any person obtaining
        a copy of this software and associated documentation files
        (the "Software"), to deal in the Software without restriction,
        including without limitation the rights to use, copy, modify,
        merge, publish, distribute, sub-license, and/or sell copies of
        the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included
        in all copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
        OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
        MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
        IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
        CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
        TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
        SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    """

    def __init__(self, number):
        super(decimal, self).__init__()
        if number is iter:
            processed = float(number[0])
        else:
            processed = float(number)
        x = 10
        while round(processed * x) != processed * x:
            x *= 10
        self.number = [processed, x]

    def __add__(self, other):
        the_other_number, num = list(other), list(self.number)
        try:
            maximum = max(
                float(num[1]), float(the_other_number[1]))
            return decimal(
                (num[0] * maximum + the_other_number[0] * maximum) / maximum)
        except IndexError:
            raise "Entered {}, which has the type {},\
             is not a valid type".format(
                other, type(other))

    def __float__(self):
        return float(self.number[0])

    def __bool__(self):
        return bool(self.number[0])

    def __str__(self):
        return str(self.number)

    def __iter__(self):
        return (x for x in self.number)

    def __repr__(self):
        return str(self.number[0])

    def __sub__(self, other):
        the_other_number, num = list(other), list(self.number)
        try:
            maximum = max(
                float(num[1]), float(the_other_number[1]))
            return decimal(
                (num[0] * maximum - the_other_number[0] * maximum) / maximum)
        except IndexError:
            raise "Entered {}, which has the type {},\
         is not a valid type".format(
                other, type(other))

    def __div__(self, other):
        the_other_number, num = list(other), list(self.number)
        try:
            maximum = max(
                float(num[1]), float(the_other_number[1]))
            return decimal(
                ((num[0] * maximum) / (
                    the_other_number[0] * maximum)) / maximum)
        except IndexError:
            raise "Entered {}, which has the type {},\
         is not a valid type".format(
                other, type(other))

    def __floordiv__(self, other):
        the_other_number, num = list(other), list(self.number)
        try:
            maximum = max(
                float(num[1]), float(the_other_number[1]))
            return decimal(
                ((num[0] * maximum) // (
                    the_other_number[0] * maximum)) / maximum)
        except IndexError:
            raise "Entered {}, which has the type {},\
         is not a valid type".format(
                other, type(other))

    def __mul__(self, other):
        the_other_number, num = list(other), list(self.number)
        try:
            maximum = max(
                float(num[1]), float(the_other_number[1]))
            return decimal(
                ((num[0] * maximum) * (
                    the_other_number[0] * maximum)) / maximum)
        except IndexError:
            raise "Entered {}, which has the type {},\
         is not a valid type".format(
                other, type(other))

    def __mod__(self, other):
        the_other_number, num = list(other), list(self.number)
        try:
            maximum = max(
                float(num[1]), float(the_other_number[1]))
            return decimal(
                ((num[0] * maximum) % (
                    the_other_number[0] * maximum)) / maximum)
        except IndexError:
            raise "Entered {}, which has the type {},\
         is not a valid type".format(
                other, type(other))
    # Pastebin: https://pastebin.com/MwzZ1W9e

我只测试了像 .1 + .1 + .1 对比 decimal(.1) + decimal(.1) + decimal(.1) 这样的简单内容,但理论上支持的所有算术类型也应该可以工作。 - theX
8
嘿,我很欣赏你为此付出的努力!希望这对你有所帮助。但是,我永远不会建议使用自行开发且大部分未经测试的代码来替代处理程序计算敏感方面的标准模块和库。 - temporary_user_name
2
我知道,但我希望将来这会有用。 - theX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接