Myślę, że dokładność i stabilność algorytmów numerycznych Highama dotyczy tego, w jaki sposób można analizować tego rodzaju problemy. Patrz rozdział 2, zwłaszcza ćwiczenie 2.8.
W tej odpowiedzi chciałbym wskazać coś, co tak naprawdę nie zostało poruszone w książce Highama (wydaje się, że nie jest to zbyt powszechnie znane). Jeśli jesteś zainteresowany udowodnieniem właściwości prostych algorytmów numerycznych takich jak te, możesz skorzystać z mocy nowoczesnych solverów SMT ( Teorie satysfakcji modulo ), takich jak z3 , używając pakietu takiego jak sbv w Haskell. Jest to nieco łatwiejsze niż używanie ołówka i papieru.
Załóżmy, że podano mi i chciałbym wiedzieć, czy spełnia . Poniższy kod Haskellz = ( x + y ) / 2 x ≤ z ≤ y0≤x≤yz=(x+y)/2x≤z≤y
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
pozwoli mi to zrobić automatycznie . Tutaj test1 fun
jest założenie , że dla wszystkich skończonej pływaki z .x , y 0 ≤ x ≤ yx≤fun(x,y)≤yx,y0≤x≤y
λ> prove $ test1 (\x y -> (x + y) / 2)
Falsifiable. Counter-example:
x = 2.3089316e36 :: Float
y = 3.379786e38 :: Float
To się przelewa. Załóżmy, że teraz biorę inną formułę: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
Nie działa (ze względu na stopniowe niedopełnienie: , co może być nieintuicyjne, ponieważ cała arytmetyka ma wartość base-2).(x/2)×2≠x
Teraz spróbuj :z=x+(y−x)/2
λ> prove $ test1 (\x y -> x + (y-x)/2)
Q.E.D.
Pracuje! Q.E.D.
To dowód , że test1
własność zachodzi dla wszystkich pływaków, jak zdefiniowano powyżej.
Co z tym samym, ale ograniczonym do (zamiast )?x≤y0≤x≤y
λ> prove $ test2 (\x y -> x + (y-x)/2)
Falsifiable. Counter-example:
x = -3.1300826e34 :: Float
y = 3.402721e38 :: Float
Okej, więc jeśli przepełni, co powiesz na ?y−xz=x+(y/2−x/2)
λ> prove $ test2 (\x y -> x + (y/2 - x/2))
Q.E.D.
Wygląda więc na to, że spośród wzorów, które tu wypróbowałem, wydaje się działać (również z dowodem). Metoda solvera SMT wydaje mi się o wiele szybszym sposobem odpowiedzi na podejrzenia dotyczące prostych wzorów zmiennoprzecinkowych niż analizowanie błędów zmiennoprzecinkowych ołówkiem i papierem.x+(y/2−x/2)
Wreszcie cel dokładności i stabilności często stoi w sprzeczności z celem wydajności. Jeśli chodzi o wydajność, tak naprawdę nie widzę, jak możesz sobie radzić lepiej niż , zwłaszcza, że kompilator nadal będzie cię ciężko tłumaczyć, tłumacząc to na instrukcje maszynowe.(x+y)/2
PS Wszystko to z arytmetyką zmiennoprzecinkową IEEE754 o pojedynczej precyzji. Sprawdziłem z podwójnej precyzji arytmetyki (wymienić z ) i działa zbyt.x≤x+(y/2−x/2)≤ySFloat
SDouble
PPS Jedną z rzeczy, o których należy pamiętać przy implementacji tego w kodzie jest to, że flagi kompilatora takie jak -ffast-math
(niektóre formy takich flag są czasami domyślnie włączone w niektórych popularnych kompilatorach) nie spowodują arytmetyki IEEE754, co unieważni powyższe dowody. Jeśli używasz flag, które umożliwiają np. Optymalizacje dodawania skojarzonego, nie ma sensu robić niczego innego niż .(x+y)/2
PPPS Dałam się trochę ponieść spojrzeniu tylko na proste wyrażenia algebraiczne bez warunków warunkowych. Don Hatch „s formuła jest ściśle lepiej.