相关性数学:(a + b)+ c!= a +(b + c)

Vai*_*gla 53 .net c# math numeric

最近,我正在阅读Eric Lippert 撰写的一篇旧博客文章,其中,在写关于关联性时,他提到在C#中,(a + b) + c并不等同于a + (b + c)a,b,c的某些值.

我无法弄清楚哪些类型和范围的算术值可能是正确的以及为什么.

xan*_*tos 78

double类型的范围:

double dbl1 = (double.MinValue + double.MaxValue) + double.MaxValue;
double dbl2 = double.MinValue + (double.MaxValue + double.MaxValue);
Run Code Online (Sandbox Code Playgroud)

第一个是double.MaxValue,第二个是double.Infinity

关于double类型的精度:

double dbl1 = (double.MinValue + double.MaxValue) + double.Epsilon;
double dbl2 = double.MinValue + (double.MaxValue + double.Epsilon);
Run Code Online (Sandbox Code Playgroud)

现在dbl1 == double.Epsilon,同时dbl2 == 0.

从字面上阅读问题:-)

checked模式中:

checked
{
    int i1 = (int.MinValue + int.MaxValue) + int.MaxValue;
}
Run Code Online (Sandbox Code Playgroud)

i1int.MaxValue

checked
{
    int temp = int.MaxValue;
    int i2 = int.MinValue + (temp + temp);
}
Run Code Online (Sandbox Code Playgroud)

(注意temp变量的使用,否则编译器会直接给出错误...技术上即使这会是一个不同的结果:-)正确编译vs不编译)

这引发了OverflowException......结果不同:- ) (int.MaxValuevs Exception)

  • 一个非常好的答案; 但它解决了数据类型_range_的限制,而我猜这个问题与其范围内的数据类型的_accuracy_更相关. (5认同)
  • @Codor添加了一个关于精度的例子. (3认同)

Luk*_*hne 14

一个例子

a = 1e-30
b = 1e+30
c = -1e+30
Run Code Online (Sandbox Code Playgroud)

  • 另一个更令人惊讶的例子是`(0.1 + 0.2)+ 0.3`和`0.1+(0.2 + 0.3)`http://www.walkingrandomly.com/?p=5380 (10认同)
  • @LưuVĩnhPhúc:这种基于精度的错误与实现有关.例如,在运行VS的Win7和运行Mono的Linux上,可能会得到不同的结果.你甚至可能没有任何错误. (3认同)

Dun*_*can 8

扩展其他答案,这些答案显示了大小数字的极端情况会得到不同的结果,这里有一个例子,其中具有真实正常数字的浮点数给出了不同的答案.

在这种情况下,我只是做了很多补充,而不是在极限精度上使用数字.做(((...(((a+b)+c)+d)+e)...或不同...(((a+b)+(c+d))+((e+f)+(g+h)))+...

我在这里使用python,但如果用C#编写,你可能会得到相同的结果.首先创建一个包含百万个值的列表,所有这些值都是0.1.从左侧添加它们,您会看到舍入错误变得很重要:

>>> numbers = [0.1]*1000000
>>> sum(numbers)
100000.00000133288
Run Code Online (Sandbox Code Playgroud)

现在再次添加它们,但这次将它们成对添加(有更多有效的方法来实现这一点,使用较少的中间存储,但我在这里保持实现简单):

>>> def pair_sum(numbers):
    if len(numbers)==1:
        return numbers[0]
    if len(numbers)%2:
        numbers.append(0)
    return pair_sum([a+b for a,b in zip(numbers[::2], numbers[1::2])])

>>> pair_sum(numbers)
100000.0
Run Code Online (Sandbox Code Playgroud)

这次任何舍入误差都会最小化.

编辑完整性,这是一个更有效但不太容易实现的成对总和.它给出了与pair_sum()上面相同的答案:

def pair_sum(seq):
    tmp = []
    for i,v in enumerate(seq):
        if i&1:
            tmp[-1] = tmp[-1] + v
            i = i + 1
            n = i & -i
            while n > 2:
                t = tmp.pop(-1)
                tmp[-1] = tmp[-1] + t
                n >>= 1
        else:
            tmp.append(v)
    while len(tmp) > 1:
        t = tmp.pop(-1)
        tmp[-1] = tmp[-1] + t
    return tmp[0]
Run Code Online (Sandbox Code Playgroud)

这是用C#编写的简单pair_sum:

using System;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static double pair_sum(double[] numbers)
        {
            if (numbers.Length==1)
            {
                return numbers[0];
            }
            var new_numbers = new double[(numbers.Length + 1) / 2];
            for (var i = 0; i < numbers.Length - 1; i += 2) {
                new_numbers[i / 2] = numbers[i] + numbers[i + 1];
            }
            if (numbers.Length%2 != 0)
            {
                new_numbers[new_numbers.Length - 1] = numbers[numbers.Length-1];
            }
            return pair_sum(new_numbers);
        }
        static void Main(string[] args)
        {
            var numbers = new double[1000000];
            for (var i = 0; i < numbers.Length; i++) numbers[i] = 0.1;
            Console.WriteLine(numbers.Sum());
            Console.WriteLine(pair_sum(numbers));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

100000.000001333
100000
Run Code Online (Sandbox Code Playgroud)