Kayan noktalı iki sayının ortalamasının sağlam hesaplanması?


15

Izin vermek x, yiki kayan nokta sayı olmak. Ortalamalarını hesaplamanın doğru yolu nedir?

Saf yol , çok büyük ve (x+y)/2aşırı olduğunda taşmalara neden olabilir . Belki daha iyi olduğunu düşünüyorum , ama iki çarpma (belki de verimsiz) içerir ve yeterince iyi olup olmadığından emin değilim. Daha iyi bir yol var mı?xy0.5 * x + 0.5 * y

Birlikte oynadığım başka bir fikir (y/2)(1 + x/y)ise x<=y. Ama yine, bunu nasıl analiz edeceğimi ve gereksinimlerimi karşıladığını kanıtlayamıyorum.

Dahası, hesaplanan ortalamanın >= min(x,y)ve olacağı garantisine ihtiyacım var <= max(x,y). İçinde sivri dışarı olarak Don Hatch'in cevap , belki bu soruyu poz daha iyi bir yoludur: Her zaman mümkün olan en doğru sonucu verir iki sayı ortalama bir uygulama nedir? Yani, kayan noktalı sayılar varsa xve ybunlar en yakın olan kayan noktalı sayılar nasıl hesaplanır (x+y)/2? Bu durumda, hesaplanan ortalama otomatik olarak >= min(x,y)ve olur <= max(x,y). Ayrıntılar için Don Hatch'in cevabına bakınız.

Not: Önceliğim sağlam doğruluktur. Verimlilik harcanabilir. Ancak, birçok sağlam ve doğru algoritma varsa, en verimli olanı seçerdim.


(+1) İlginç bir soru, şaşırtıcı derecede önemsiz değil.
Kirill

1
Geçmişte, kayar nokta değerleri ara sonuçlar için daha yüksek bir hassasiyet formunda hesaplanmış ve tutulmuştur. A + b (64 bit iki katına çıkar), 80 bit ara sonuç üretirse ve bu 2'ye bölünürse, taşma konusunda endişelenmenize gerek yoktur. Hassasiyet kaybı daha az belirgindir.
JDługosz

Bunun çözümü nispeten basit görünüyor ( bir cevap ekledim ). Mesele şu ki, ben bir bilgisayar bilimleri uzmanı değil, bir programcıyım, bu yüzden bu soruyu çok daha zorlaştıran eksik olan ne?
IQAndreas

İki çarpma ve bölme maliyeti hakkında endişelenmeyin; derleyiciniz bunları sizin için optimize eder.
Federico Poloni

Yanıtlar:


18

Sanırım Higham'ın Sayısal Algoritmaların Doğruluğu ve Kararlılığı, bu tür problemleri nasıl analiz edebileceğini ele alıyor. Bkz. Bölüm 2, özellikle egzersiz 2.8.

Bu cevapta Higham'ın kitabında gerçekten ele alınmayan bir şeyi belirtmek istiyorum (bu konuda çok fazla bilinmemektedir). Eğer ilgilenen varsa ispat Bunlar gibi basit sayısal algoritmaların özelliklerini, modern SMT çözenler (gücünü kullanabilir Gerçeklenebilirlik Modülo Teorileri gibi), z3 gibi bir paket kullanarak, SBV Haskell. Bu, kalem ve kağıt kullanmaktan biraz daha kolaydır.

O verileni varsayalım ve bilmek istiyorum ise Z = ( x + y ) / 2 tatmin x z y . Aşağıdaki Haskell kodu0xyz=(x+y)/2xzy

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

bunu otomatik olarak yapmama izin verecek . Burada test1 funbir önerme olduğu her sonlu mantarlar için x , y ile 0 x y .xfun(x,y)yx,y0xy

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

Taşar. Şimdi diğer formülünüzü aldığımı varsayalım: 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

Çalışmaz (kademeli alt akış nedeniyle: , tüm aritmetik taban-2 olması nedeniyle sezgisel olmayabilir).(x/2)×2x

Şimdi deneyin :z=x+(yx)/2

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

İşler! Bu Q.E.D., özelliğin yukarıda tanımlandığı gibi tüm şamandıralar için sahip olduğunun bir kanıtıdırtest1 .

Peki ya aynı, ama sınırlı ( 0 x y yerine )?xy0xy

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

Tamam, eğer taşarsa, z = x + ( y / 2 - x / 2 ) ?yxz=x+(y/2x/2)

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

Bu yüzden burada denediğim formüller arasında işe yarıyor gibi görünüyor (bir kanıtla da). SMT çözücü yaklaşımı bana basit kayan nokta formülleri hakkındaki şüpheleri, kalem ve kağıtla kayan nokta hata analizinden geçmekten çok daha hızlı bir yol gibi geliyor.x+(y/2x/2)

Son olarak, doğruluk ve istikrar hedefi genellikle performans hedefi ile çelişmektedir. Performans için, daha iyi nasıl yapabileceğinizi gerçekten görmüyorum , özellikle de derleyici yine de bunu sizin için makine talimatlarına çevirmenin ağır kaldırmasını yapacak.(x+y)/2

PS Tüm bunlar tek duyarlıklı IEEE754 kayan noktalı aritmetiktir. Bildiğim kadarıyla çift hassas aritmetik (yerine sahip olan ), ve çok çalışır.xx+(y/2x/2)ySFloatSDouble

PPS Bunu kodda uygularken akılda tutulması gereken bir şey, derleyici bayrakları gibi -ffast-math(bu tür bayrakların bazı biçimleri bazen bazı ortak derleyicilerde varsayılan olarak açıktır ), yukarıdaki kanıtları geçersiz kılacak olan IEEE754 aritmetiği ile sonuçlanmaz. İlişkili ekleme optimizasyonlarını etkinleştiren bayraklar kullanırsanız, dışında bir şey yapmanın bir anlamı yoktur .(x+y)/2

PPPS Biraz koşullu olmayan basit cebirsel ifadelere baktım. Don Hatch 'ın formülü kesinlikle daha iyidir.


2
Tut; x <= y (x> = 0 olsun ya da olmasın) o zaman x + (y / 2-x / 2) yapmanın iyi bir yol olduğunu iddia ettiniz mi? Cevabın tam olarak temsil edilebildiği aşağıdaki durumda yanlış cevabı verdiği için doğru olamıyor gibi görünüyor: x = -1, y = 1 + 2 ^ -52 (1'den büyük en küçük temsil edilebilir sayı), bu durumda cevap 2 ^ -53'tür. Python'da doğrulama: >>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Don Hatch

2
x(x+y)/2yx,y(x+y)/2(x+y)/2

8

İlk olarak, her durumda en doğru cevabı veren bir yönteminiz varsa, gerekli koşulunuzu karşılayacağını gözlemleyin. (Not diyorum o bir yerine en doğru cevabı en doğru yanıtı, çünkü iki kazananlar olabilir.) Kanıt: Eğer aksine, sen gelmez doğru-as-mümkün cevabı değil , bu gerekli koşulu karşılayan araca ya (durumda olan ya da daha iyi bir yanıt, bir çelişki) (durumda olan daha iyi bir yanıt, bir çelişki).answer<min(x,y)<=max(x,y)min(x,y)min(x,y)<=max(x,y)<answermax(x,y)

Bence bu, sorunuzun mümkün olan en doğru cevabı bulmak anlamına geldiği anlamına geliyor. Boyunca IEEE754 aritmetiği varsayarsak, aşağıdakileri öneririm:

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

Bunun en doğru cevabı verdiği iddiam biraz yorucu bir vaka analizi. İşte gidiyor:

  • Durum max(abs(x),abs(y)) >= 1.:

    • Küçük harf ne x ne de y denormalize edilmez: Bu durumda hesaplanan cevap x/2.+y/2.aynı mantisleri manipüle eder ve bu nedenle (x+y)/2taşmayı önlemek için genişletilmiş üsler varsayırsak verimin hesaplanmasıyla aynı cevabı verir . Bu cevap yuvarlama moduna bağlı olabilir, ancak her durumda IEEE754 tarafından mümkün olan en iyi cevap olacağı garanti edilir (hesaplananın x+ymatematiksel x + y'ye en iyi yaklaşım olduğu garanti edilir ve 2'de bölünme tam olarak budur durum).
    • X küçük harf denormalizedir (ve böylece 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.

    • Küçük harf y denormalizedir (ve benzeri abs(x)>=1): benzer.

  • Durum max(abs(x),abs(y)) < 1.:
    • Hesaplanan küçük harf, x+ydenormalize edilmemiş veya denormalize edilmiştir ve hatta " eşittir ": Hesaplanmış x+ykesin olmasa da, IEEE754 tarafından matematiksel x + y'ye mümkün olan en iyi yaklaşım olduğu garanti edilmektedir. Bu durumda ifadede sonraki 2'ye bölünme (x+y)/2.kesindir, bu nedenle hesaplanan cevap (x+y)/2.matematiksel (x + y) / 2'ye mümkün olan en iyi yaklaşımdır.
    • Hesaplanan Subcase x+y"tek" normalleştirilmemiş ve: Bu durumda, tam x biri, y, normalleştirilmemiş-ve- olmalıdır "tek", x diğer anlamına gelir, Y karşıt işareti olan bir normalleştirilmemiş edilir ve bilgisayarlı çok x+yisimli tam olarak matematiksel x + (x+y)/2.y'dir ve böylece hesaplanan IEEE754 tarafından matematiksel (x + y) / 2'ye mümkün olan en iyi yaklaşım olarak garanti edilir.

"Denormalize" dediğimde, başka bir şey demek istediğimi anlıyorum - yani, sayılara ulaştıkça birbirine yakın olan sayılar, yani normalden farklı sayılardan kabaca iki kat daha büyük olan sayıların aralığı, yani en.wikipedia.org/wiki/Denormal_number adresindeki diyagramdaki ilk 8 keneler . Mesele şu ki, bunların "garip" olanları, ikiye bölmenin kesin olmadığı tek sayılardır. Bunu netleştirmek için cevabın bu kısmını yeniden ifade etmem gerekiyor.
Don Hatch

Aşırı / düşük taşma olmadığında, her zaman olduğunu belirterek analizinizi basitleştirebilirsiniz.fl(Öp(x,y))=Öp(x,y)(1+δ) nerede |δ|uve 2'ye bölünmeler denormal olmayan sayılar için kesindir. Bu her ikisinin dex/2+y/2 ve (x+y)/2her zaman doğru bir şekilde yuvarlanır, aşırı / düşük taşma olmaz, geriye kalan tek şey aşırı / düşük taşmalar göstermektir, bu da kolaydır.
Kirill

@Kirill Biraz kayboldum ... nereden geldin? Ayrıca, "2'ye bölme denormal olmayan sayılar için kesin" olduğunu doğru düşünmüyorum ... Bu, takıldığım aynı şey ve doğru bulmaya çalışmak biraz garip görünüyor. Kesin ifade "abs / x en büyük alt normal sayının en az iki katı olduğu sürece" x / 2 tam "gibi bir şeydir ... argh, garip!
Don Hatch

3

binary64(Çift kesinlikli) hesaplama ile örneklenen IEEE-754 ikili kayan noktalı formatlar için S. Boldo, aşağıda gösterilen basit algoritmanın doğru yuvarlak ortalama verdiğini resmen kanıtlamıştır.

Sylvie Boldo, "Kayan nokta ortalamasını hesaplayan programların resmi olarak doğrulanması." In Formal Mühendislik Yöntemleri Konferansı , s. 17-32. Springer, Cham, 2015. ( çevrimiçi taslak )

İkili bölme noktası aritmetiğinde ikiye bölünme doğru olduğundan , altta taşma gerçekleşmediği için , iki formülden birini seçerek sezgisel olarak net görünüyor(x+y)/2 ve x/2+y/2uygun şekilde (girdilerin büyüklüğüne bağlı olarak) bir kez doğru şekilde yuvarlanmış bir ortalama elde etmelidir. Boldo'nun makalesi, IEEE-754 için binary64herhangi bir geçiş noktasınınC[2-967,2970]yeterli olacaktır. Biri seçebilirC Belirli bir kullanım durumu için en iyi performansı sağlamak için.

Bu, aşağıdaki örnek ISO-C99kodu verir :

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);
}

Son yapılan takip çalışmalarında, S. Boldo ve ortak yazarlar, kaynaşmış çarpma-ekleme (FMA) işlemlerini ve iyi bilinen bir hassasiyeti kullanarak IEEE-754 ondalık kayan nokta biçimleri için mümkün olan en iyi sonuçların nasıl elde edileceğini gösterdiler. iki katına yapı taşı (TwoSum):

Sylvie Boldo, Florian Faissole ve Vincent Tourneur, "Ondalık Kayan Nokta Sayılarının Doğru Ortalamasını Hesaplamak İçin Resmi Olarak Kanıtlanmış Bir Algoritma." In Bilgisayar Aritmetik (arith 25) üzerinde 25 IEEE Sempozyumu , Haziran 2018, s. 69-75. ( çevrimiçi taslak )


2

Süper verimli performans açısından olmasa da, (1) sayıların hiçbirinin ikisinden büyük olmamasını xveya y(taşma olmamasını) ve (2) kayan noktayı "doğru" tutmak için çok basit bir yol vardır. mümkün (ve (3) , ek bir bonus olarak, çıkarma kullanılsa bile, hiçbir değer negatif sayılar olarak saklanmayacaktır.

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

Aslında, doğruluk için gerçekten gitmek istiyorsanız, bölünmeyi yerinde yapmanız bile gerekmez; Sadece değerlerini döndürür min(x, y)ve differencemantıksal basitleştirmek veya daha sonra işlemek için hangi kullanabilirsiniz.


What I'm trying to figure out now is how to make this same answer work with more than two items, while keeping all variables staying lower than the greatest of the numbers, and using only one division operation to preserve accuracy.
IQAndreas

@becko Yup, you would be doing division at least twice. Also, the example you gave would make the answer come out wrong. Imagine the mean of 2,4,9, it is not the same as the mean of 3,9.
IQAndreas

You're right, my recursion was wrong. I'm not sure how to fix it right now, without losing precision.
becko

Can you prove that this gives the most accurate possible result? That is, if x and y are floating-point, your computation produces a floating-point closest to (x+y)/2?
becko

1
Won't this overflow when x,y are the least and greatest expressible numbers?
Don Hatch

1

Convert to higher precission, add the values there and convert back.

There should be no overflow in the higher precission and if both are in the valid floating point range, the calculated number should be inside too.

And it should be in between them, worst case just half of the larger number if the precission is not sufficient.


This is the brute force approach. It probably works, but I was looking for an analysis that didn't require intermediate higher precision. Also, can you estimate how much intermediate higher precision is required? In any case, don't delete this answer (+1), I just won't accept it as the answer.
becko

1

Theoretically, x/2 can be computed by subtracting 1 from the mantissa.

However, actually implementing bitwise operations like this is not necessarily straightforward, particularly if you don't know the format of your floating point numbers.

Bunu yapabiliyorsanız, tüm işlem 3 toplama / çıkarma işlemine düşürülür, bu da önemli bir gelişme olmalıdır.


0

@Roland Heath ile aynı çizgide düşünüyordum ama henüz yorum yapamıyorum, işte benim almam:

x/21 çıkartılarak hesaplanabilmektedir üs (mantis 1 çıkarılması değil, mantis çıkartmaktır 2^(value_of_exponent-length_of_mantissa)genel değerinden).

Genel davanın kısıtlaması olmadan, varsayalım x < y. (If x > y, değişkenleri yeniden etiketleyin. If x = y, (x+y) / 2önemsizdir.)

  • Transform (x+y) / 2içine x/2 + y/2, (üs teker) iki tamsayı çıkarılması ile gerçekleştirilebilir ki
    • Ancak, temsilinize bağlı olarak üs üzerinde bir alt sınır vardır. 1 çıkartmadan önce üssünüz zaten minimum ise, bu yöntem özel durum işleme gerektirir. Minimum bir üs xyapacakx/2 smaller than representable (assuming mantissa is represented with an implicit leading 1).
    • xVardiya değerinden 1 çıkarmak yerine , shiftx (varsa ve örtük lider 1 ekleyin) birer sağındaki 'ın mantiste.
    • Çok az değilse, y'nin üssünden 1 çıkarın. Minimal ise (y, mantis yüzünden x'den büyükse), mantisayı bir sağa kaydırın (varsa örtük 1'i ekleyin).
    • Üssüne göre yeni mantisini xsağa kaydırıny.
    • Mantis tamamen çıkarılmadıkça mantis üzerinde tamsayı ekleme yapın x. Eğer her iki üs en az ise, önde gelenler taşacak, bu tamam, çünkü o taşmanın yine örtük bir öncü olması gerekiyordu.
  • ve bir kayan nokta ilavesi.
    • Can't think of any special case here; except for rounding, which also applies to shifting described above.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.