Posts mit dem Label BINARY_FLOAT werden angezeigt. Alle Posts anzeigen
Posts mit dem Label BINARY_FLOAT werden angezeigt. Alle Posts anzeigen

Donnerstag, 13. Oktober 2011

Binäre Genauigkeit bei der Verwendung von BINARY_DOUBLE

Heute geht's um die Verwendung der Datentypen BINARY_FLOAT bzw. BINARY_DOUBLE und eine mögliche Fehlerquelle. Der folgende anonyme PL/SQL-Block demonstriert das Problem:
declare
 v_value binary_double := 0.0d;
begin
 for i in 1 .. 10 loop
  v_value := v_value + 0.1d;
 end loop;
 if (v_value = 1.0d) then
  dbms_output.put_line('v_value  = 1.0');
 else
  dbms_output.put_line('v_value != 1.0');
 end if;
end;
/
v_value != 1.0
Das Ergebnis verwundert, denn man erwartet, dass 10 mal 0,1 genau 1 ergibt. Die Ursache liegt in der binären Darstellung von 0,1; die Datentypen besitzen eben "nur" eine binäre Genauigkeit.

Statt einem Vergleich auf Gleichheit, der bei Gleitkommazahlen generell nicht zu empfehlen ist, überprüft man, ob die absolute Abweichung zum Vergleichswert sehr gering ist. Die gewählte Genauigkeit ist je nach Anwendung verschieden; zum Beispiel 10-8.

Durch eine Anpassung des vorherigen PL/SQL-Blocks erhält man nun das erwartete Ergebnis:
declare
 v_value binary_double := 0.0d;
begin
 for i in 1 .. 10 loop
  v_value := v_value + 0.1d;
 end loop;
 if (abs(v_value - 1.0d) < 1e-8d) then
  dbms_output.put_line('v_value  = 1.0');
 else
  dbms_output.put_line('v_value != 1.0');
 end if;
end;
/
v_value  = 1.0
Zur Vereinfachung könnte man diese Funktionalität in eine benannte Funktion auslagern:
create or replace function equal(
 p_value_in in binary_double,
 p_comparison_value_in in binary_double,
 p_precision_in in binary_double default 1e-8d
)
return boolean is
begin
 return abs(p_value_in - p_comparison_value_in) < p_precision_in;
end equal;
/

Mittwoch, 3. August 2011

BINARY_DOUBLE und die implizite Typ-Konvertierung

Heute geht's um die Verwendung des Datentyps BINARY_DOUBLE bzw. BINARY_FLOAT und die Vermeidung von impliziter Typ-Konvertierung.

Als Beispiel dient das Wallis-Produkt zur Berechnung der Kreiszahl Pi. Die Berechnung erfolgt durch den folgenden anonymen PL/SQL-Block:
declare
 v_pi binary_double := 1.0;
begin
 for i in 1 .. 1000000 loop
  v_pi := v_pi * (1.0 + (1.0 / (4.0 * i * i - 1.0)));
 end loop;
 v_pi := v_pi * 2.0;
 dbms_output.put_line(v_pi);
end;
/
3,1415918681921307E+000

PL/SQL-Prozedur erfolgreich abgeschlossen.

Abgelaufen: 00:00:05.07
Die Berechnung dauert ungefähr fünf Sekunden und gibt die Zahl Pi bis auf fünf Stellen genau an. Doch an welcher Stelle befindet sich nun das Optimierungspotenzial?

Literale vom Typ BINARY_DOUBLE bzw. BINARY_FLOAT enthalten den Buchstaben d (D) bzw. f (F). Ansonsten handelt es sich um Literale vom Typ NUMBER, die im vorherigen PL/SQL-Block zur Typ-Konvertierung geführt haben.

Der angepasste PL/SQL-Block sieht dann wie folgt aus:
declare
 v_pi binary_double := 1.0d;
begin
 for i in 1 .. 1000000 loop
  v_pi := v_pi * (1.0d + (1.0d / (4.0d * i * i - 1.0d)));
 end loop;
 v_pi := v_pi * 2.0d;
 dbms_output.put_line(v_pi);
end;
/
3,1415918681921489E+000

PL/SQL-Prozedur erfolgreich abgeschlossen.

Abgelaufen: 00:00:00.46
Jetzt reduziert sich die Dauer der Berechnung deutlich auf unter eine Sekunde.

Also sollte man bei der Verwendung von BINARY_DOUBLE bzw. BINARY_FLOAT stets auf die korrekte Angabe von Literalen achten.