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

时间:2015-08-14 07:05:20

标签: c# .net math numeric

最近我经历了old blog post by Eric Lippert 其中,在写关于关联性时,他提到在C#中,(a + b) + c对于a,b,c的某些值不等同于a + (b + c)

我无法弄清楚哪些类型和范围的算术值可能是正确的以及原因。

7 个答案:

答案 0 :(得分:78)

double类型的范围内:

double dbl1 = (double.MinValue + double.MaxValue) + double.MaxValue;
double dbl2 = double.MinValue + (double.MaxValue + double.MaxValue);

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

关于double类型的精度:

double dbl1 = (double.MinValue + double.MaxValue) + double.Epsilon;
double dbl2 = double.MinValue + (double.MaxValue + double.Epsilon);

现在dbl1 == double.Epsilon,而dbl2 == 0

从字面上解读问题: - )

checked模式下:

checked
{
    int i1 = (int.MinValue + int.MaxValue) + int.MaxValue;
}

i1int.MaxValue

checked
{
    int temp = int.MaxValue;
    int i2 = int.MinValue + (temp + temp);
}

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

这会引发OverflowException ...结果不同:-)(int.MaxValue vs Exception

答案 1 :(得分:14)

一个例子

a = 1e-30
b = 1e+30
c = -1e+30

答案 2 :(得分: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

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

>>> 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

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

编辑的完整性,这是一个更有效但不太容易实现的成对总和。它给出了与上面的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]

这是用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));
        }
    }
}

带输出:

100000.000001333
100000

答案 3 :(得分:4)

这源于普通值类型(int,long等)使用固定数量的字节存储的事实。因此,当两个值之和超过字节存储容量时,可能会溢出。

在C#中,可以使用BigInteger来避免此类问题。 BigInteger的大小是任意的,因此不会产生溢出。

BigInteger仅适用于.NET 4.0及更高版本(VS 2010 +)。

答案 4 :(得分:0)

简短回答是WITH BomTree AS ( SELECT bomh.ITEMNO ,itm1.[DESC] ,bomh.BOMNO ,bomh.BUILDQTY ,bomh.UNIT ,bomd.COMPONENT ,itm.[DESC] [Comp Desc] ,cast(bomd.QTY as int) [Comp-Qty] --cast as integer ,bomd.COMPBOMNO ,0 AS Depth FROM ICBOMH bomh INNER JOIN ICBOMD bomd ON bomh.BOMNO = bomd.BOMNO AND bomh.ITEMNO = bomd.ITEMNO INNER JOIN ICITEM itm1 ON bomd.ITEMNO = itm1.ITEMNO INNER JOIN ICITEM itm ON bomd.COMPONENT = itm.ITEMNO WHERE bomd.BOMNO = '01' AND bomd.ITEMNO = '300060397' UNION ALL SELECT bomh.ITEMNO ,itm1.DESC] ,bomh.BOMNO ,bomh.BUILDQTY ,bomh.UNIT ,bomd.COMPONENT ,itm.[DESC] [Comp Desc] ,cast((t.[Comp-Qty] * bomd.QTY) as int) AS [Comp-Qty] --cast as integer ,bomd.COMPBOMNO ,t.Depth + 1 AS Depth FROM ICBOMH bomh INNER JOIN ICBOMD bomd ON bomh.BOMNO = bomd.BOMNO AND bomh.ITEMNO = bomd.ITEMNO INNER JOIN ICITEM itm1 ON bomd.ITEMNO = itm1.ITEMNO INNER JOIN ICITEM itm ON bomd.COMPONENT = itm.ITEMNO INNER JOIN BomTree AS t ON bomd.ITEMNO = t.COMPONENT ) SELECT DISTINCT ITEMNO ,[DESC] ,BOMNO ,BUILDQTY ,UNIT ,COMPONENT ,[Comp Desc] ,[Comp-Qty] ,COMPBOMNO ,Depth FROM BomTree; 数学上的,但不一定是计算上的。

记住计算机确实以二进制方式工作,即使是简单的小数也可能在转换为内部格式时导致舍入错误。

根据语言的不同,即使添加也会产生舍入误差,在上例中,(a + b) + c == a + (b + c)中的舍入误差可能与a+b中的舍入误差不同。

一个令人惊讶的罪犯是JavaScript:b+c。舍入误差在小数点后面很长,但是真实且有问题。

通过首先添加小部件来减少舍入误差是一般原则。这样他们就可以在被更大数字淹没之前积累。

答案 5 :(得分:0)

几个类似的例子:

static void A(string s, int i, int j)
{
  var test1 = (s + i) + j;
  var test2 = s + (i + j);
  var testX = s + i + j;
}

此处A("Hello", 3, 5)导致test1testX等于"Hello35",而test2"Hello8"

static void B(int i, int j, long k)
{
  var test1 = (i + j) + k;
  var test2 = i + (j + k);
  var testX = i + j + k;
}

此处B(2000000000, 2000000000, 42L)导致test1testX-294967254L模式下等同于unchecked,而test2变为4000000042L }。

答案 6 :(得分:0)

在同一个博客的评论中我发现了这个,C#中的“^”符号是什么意思

a = 10 ^ x

b = -a

c = 5

(a+b) + c == 5

a + (b+c) == 0