浮点数中两个数字的均值的稳健计算?


15

xy是两个浮点数。什么是计算均值的正确方法?

天真的方法(x+y)/2会在xy太大时导致溢出。我认为0.5 * x + 0.5 * y可能更好,但是它涉及两个乘法(可能效率不高),我不确定它是否足够好。有没有更好的办法?

我一直在玩的另一个想法是(y/2)(1 + x/y)if x<=y。但是,我不确定如何对此进行分析并证明它满足我的要求。

此外,我需要保证计算的均值将为>= min(x,y)<= max(x,y)。正如唐·哈奇(Don Hatch)的回答所指出那样,提出这个问题的一种更好的方法可能是:两个数字均值的实现是什么,总是能给出最准确的结果?也就是说,如果xy是浮点数,如何计算最接近的浮点数(x+y)/2?在这种情况下,计算的平均值将自动为>= min(x,y)<= max(x,y)。有关详细信息,请参见Don Hatch的答案

注意:我的首要任务是确保准确性。效率是消耗性的。但是,如果有许多健壮且准确的算法,我会选择最有效的算法。


(+1)有趣的问题,令人惊讶的是微不足道的。
基里尔

1
过去,计算浮点值并以更高的精度形式保存中间结果。如果a + b(64位双精度)产生80位中间结果,并且这是2的结果,则不必担心溢出。精度损失不太明显。
JDługosz

解决方案似乎相对简单(我添加了一个答案)。问题是我是程序员,而不是计算机科学专家,所以我错过了什么,这个问题变得如此困难?
IQAndreas 2015年

不用担心乘除二的代价;您的编译器会为您优化它们。
Federico Poloni 2015年

Answers:


18

我认为Higham的数值算法准确性和稳定性解决了人们如何分析这些类型的问题。参见第2章,特别是练习2.8。

在此答案中,我想指出一些在Higham的书中并未真正解决的问题(就此而言,它似乎并不广为人知)。如果你有兴趣的证明,如这些简单的数值算法性能,可以使用现代SMT求解器(功率可满足模理论),如Z3,采用了包,如SBV在Haskell。这比使用铅笔和纸要容易一些。

假设我假定,我想知道如果ž = X + Ý / 2满足X Ž ÿ。以下Haskell代码0xyz=(x+y)/2xzÿ

import Data.SBV

test1 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test1 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ 0 .<= x &&& x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

test2 :: (SFloat -> SFloat -> SFloat) -> Symbolic SBool
test2 fun =
  do [x, y] <- sFloats ["x", "y"]
     constrain $ bnot (isInfiniteFP x) &&& bnot (isInfiniteFP y)
     constrain $ x .<= y
     let z = fun x y
     return $ x .<= z &&& z .<= y

让我自动做到这一点。这里test1 fun命题所有有限浮X ÿ0 X ÿxfun(x,y)yx,y0xy

λ> prove $ test1 (\x y -> (x + y) / 2)
Falsifiable. Counter-example:
  x = 2.3089316e36 :: Float
  y = 3.379786e38 :: Float

它溢出了。假设我现在采用您的其他公式:z=x/2+y/2

λ> prove $ test1 (\x y -> x/2 + y/2)
Falsifiable. Counter-example:
  x = 2.3509886e-38 :: Float
  y = 2.3509886e-38 :: Float

不起作用(由于逐渐下溢:,由于所有算术均以2为底,因此可能不直观)。(x/2)×2x

现在尝试z=x+(yx)/2

λ> prove $ test1 (\x y -> x + (y-x)/2)
Q.E.D.

作品!该Q.E.D.证明test1财产所有花车持有如上定义。

什么是相同的,但仅限于(而不是0 X ÿ)?xy0xy

λ> prove $ test2 (\x y -> x + (y-x)/2)
Falsifiable. Counter-example:
  x = -3.1300826e34 :: Float
  y = 3.402721e38 :: Float

好的,如果溢出,那么z = x + y / 2 x / 2 怎么样?yxz=x+(y/2x/2)

λ> prove $ test2 (\x y -> x + (y/2 - x/2))
Q.E.D.

因此,似乎在我尝试过的公式中,似乎也起作用(也有证明)。在我看来,SMT求解器方法比使用铅笔和纸进行浮点误差分析来回答有关简单浮点公式的怀疑要快得多。x+(y/2x/2)

最后,准确性和稳定性的目标通常与性能目标不一致。为了提高性能,我真的没有看到如何比做得更好,尤其是因为编译器仍然会为您将其翻译成机器指令而费劲。(x+y)/2

PS这就是单精度IEEE754浮点运算的全部。我检查与双精度运算(替换用),和它的作品了。xx+(y/2x/2)ySFloatSDouble

PPS在代码中实现此功能时要记住的一件事是,像这样的编译器标志-ffast-math(某些形式的此类标志有时在某些常见的编译器中默认为打开)不会导致IEEE754算术,这会使上述证明无效。如果您确实使用了启用关联加法优化的标志,那么除了之外,别无他法。(x+y)/2

PPPS让我有些困惑,只看了没有条件的简单代数表达式。Don Hatch公式严格更好。


2
坚持,稍等; 您是否声称如果x <= y(无论x> = 0与否),那么x +(y / 2-x / 2)就是一个好方法吗?在我看来这是不对的,因为在以下情况下,当答案完全可表示时,它会给出错误的答案:x = -1,y = 1 + 2 ^ -52(最小可表示数字大于1),在这种情况下,答案是2 ^ -53。在python中进行确认: >>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Don Hatch 2015年

2
XX+ÿ/2ÿXÿX+ÿ/2X+ÿ/2

8

首先,请注意,如果您有一种在所有情况下都能给出最准确答案的方法,那么它将满足您的条件。(请注意,我说的一个最准确的答案,而不是最准确的答案,因为可能有两个赢家。)证明:如果,相反,你有一个准确-AS-可能的答案,它不能满足所需条件,即意思是answer<min(x,y)<=max(x,y)(在这种情况下min(x,y)是一个更好的答案,一个矛盾)或min(x,y)<=max(x,y)<answer(在这种情况下max(x,y)是一个更好的答案,一个矛盾)。

因此,我认为这意味着您的问题可以归结为找到最准确的可能答案。整个假设为IEEE754算法,我提出以下建议:

if max(abs(x),abs(y)) >= 1.:
    return x/2. + y/2.
else:
    return (x+y)/2.

我认为这给出了最准确的答案,这是一个乏味的案例分析。开始:

  • 案例max(abs(x),abs(y)) >= 1.

    • 在x和y都没有被归一化的子情况下:在这种情况下,计算出的答案x/2.+y/2.操纵相同的尾数,因此,(x+y)/2如果我们假设扩展指数以防止溢出,则得出的答案与产生的完全相同。该答案可能取决于舍入模式,但是在任何情况下,IEEE754都将其保证为最佳答案(因为x+y所保证的计算结果是对数学x + y的最佳近似值,在这种情况下,除以2就是精确的事实案件)。
    • 子案例x被去规格化了(以此类推abs(y)>=1):

      answer = x/2. + y/2. = y/2. since abs(x/2.) is so tiny compared to abs(y/2.) = the exact mathematical value of y/2 = a best possible answer.

    • 子情况y被去规格化(以此类推abs(x)>=1):类似。

  • 案例max(abs(x),abs(y)) < 1.
    • 在子情况下,计算所得的x+y值可以是非归一化的或“ x+y归一化且为偶数的”:尽管计算出的值可能不精确,但IEEE754保证它是对数学x + y的最佳近似。在这种情况下,表达式中随后的2分(x+y)/2.是精确的,因此计算得出的答案(x+y)/2.是对数学(x + y)/ 2的最佳近似。
    • x+y在子情况下,计算所得的值将被归一化并加“奇数”:在这种情况下,x,y之一也必须被归一化且“奇数”,这意味着x,y的另一个将以相反的符号进行归一化,因此计算出x+y的恰好是数学的x + y,因此计算得出(x+y)/2. IEEE754保证所得的值是数学(x + y)/ 2的最佳近似值。

我意识到当我说“非正规化”时,我的意思真的是别的东西-也就是说,数字彼此之间的距离与数字所获得的距离一样近,即数字范围大约是非正规化数字范围的两倍,即,图中en.wikipedia.org/wiki/Denormal_number的前8个刻度。关键是,这些中的“奇数”是唯一将其除以2的数字。我需要重新表述答案的这一部分,以使内容更清楚。
唐·哈奇

FØpXÿ=ØpXÿ1个+δ|δ|üX/2+ÿ/2X+ÿ/2总是正确舍入,没有上溢/下溢,剩下的就是不显示上溢/下溢,这很容易。
基里尔2015年

@基里尔我有点迷路...你来自哪里?而且我也不认为“除以2的除数对非正规数是准确的”是不对的……这是我绊倒的事情,尝试使其正确似乎有点尴尬。精确的语句更像是“只要abs(x)至少是最大的次正规数的两倍,x / 2就是精确的”……哎呀,尴尬!
唐·哈奇

3

对于以binary64(双精度)计算为例的IEEE-754二进制浮点格式,S。Boldo正式证明以下所示的简单算法可提供正确的舍入平均值。

Sylvie Boldo,“计算浮点平均值的程序的形式验证。” 在国际形式工程方法会议上,第17-32页。占卜·斯普林格,2015年。(在线草案

X+ÿ/2X/2+ÿ/2binary64C[2-9672970]C 以便为特定用例提供最佳性能。

这产生了以下示例 ISO-C99代码:

double average (double x, double y) 
{
    const double C = 1; /* 0x1p-967 <= C <= 0x1p970 */
    return (C <= fabs (x)) ? (x / 2 + y / 2) : ((x + y) / 2);
}

在最近的后续工作中,S。Boldo及其合作者展示了如何通过使用融合乘法加法(FMA)操作和众所周知的精度-来为IEEE-754十进制浮点格式获得最佳结果。加倍的构建基块(TwoSum):

Sylvie Boldo,Florian Faissole和Vincent Tourneur,“一种经过正式验证的算法,可以计算十进制浮点数的正确平均值。” 在2018年6月第25届IEEE计算机算术研讨会(ARITH 25)中,第69-75页。(在线草稿


2

尽管这可能不是高效的性能,但有一种非常简单的方法来(1)确保所有数字都不大于x或等于y((无溢出)),并且(2)将浮点保持为“准确”,即可能的(和(3)作为补充,即使使用减法,也不会将任何值存储为负数。

float difference = max(x, y) - min(x, y);
return min(x, y) + (difference / 2.0);

实际上,如果您确实想提高准确性,则甚至无需现场进行除法;刚刚返回的值,min(x, y)并且difference您可以使用逻辑简化或更高版本的操纵。


我现在想弄清楚的是,如何使相同的答案适用于两个以上的项目,同时使所有变量保持低于最大值,并且仅使用一个除法运算来保持准确性。
IQAndreas 2015年

@becko是的,您至少要进行两次除法。同样,您给出的示例会使答案出问题。想象的平均值2,4,9与的平均值不同3,9
IQAndreas 2015年

您是对的,我的递归是错误的。我不确定如何立即修复它,而又不损失精度。
becko

您能证明这给出了最准确的结果吗?也就是说,如果xy是浮点,则您的计算将产生最接近(x+y)/2?的浮点。
becko

1
当x,y是最小和最大可表示数字时,会不会溢出?
唐·哈奇

1

转换为更高的精度,在其中添加值并转换回去。

较高的精度不应有溢出,并且如果两者都在有效的浮点范围内,则计算的数字也应在内部。

而且应该在它们之间,最坏的情况是,如果没有足够的精确度,则只能是较大数量的一半。


这是蛮力方法。它可能有效,但是我正在寻找不需要中间更高精度的分析。另外,您可以估计需要多少中间更高的精度吗?无论如何,不​​要删除此答案(+1),我只是不接受它作为答案。
becko 2015年

1

从理论上讲 x/2可以通过从尾数中减去1来计算。

但是,实际上实现这样的按位运算不一定很简单,尤其是在您不知道浮点数格式的情况下。

如果可以这样做,整个操作将减少为3个加法/减法,这将是一个很大的改进。


0

我当时的想法与@Roland Heath相同,但目前还无法发表评论,这是我的看法:

x/2可以通过从指数中减去1来计算(不是尾数,从尾数中减去1就是减去2^(value_of_exponent-length_of_mantissa)从总值中)。

在不受一般情况限制的情况下,让我们假设x < y。(如果为x > y,则重新标记变量。如果为x = y(x+y) / 2则很简单。)

  • 转换(x+y) / 2x/2 + y/2,可以通过两个整数减法(从指数中减一)来执行
    • 但是,根据您的表示形式,指数有一个下限。如果在减1之前您的指数已经很小,则此方法将需要特殊情况处理。最小指数x将使x/2小于可表示的(假设尾数以隐式前导1表示)。
    • 而不是从的指数中减去1 x,请移动x接一个的尾数向右(并添加隐含的领先的1,如果有的话)。
    • 如果不是最小,则从y的指数中减去1。如果最小值最小(由于尾数,y大于x),则将尾数向右移动一(添加隐式前导1,如果有的话)。
    • x根据的指数向右移动新的尾数y
    • 除非尾数的尾数x已完全移出,否则对尾数执行整数加法。如果两个指数都最小,那么前者将溢出,这是可以的,因为该溢出应该再次成为隐式前导。
  • 和一个浮点数加法。
    • 在这里想不到任何特殊情况;除四舍五入外,这也适用于上述移位。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.