30. Mai 2007

Fließkommazahlen: NUMBER, BINARY_FLOAT, BINARY_DOUBLE ... oder was ...?

Seit Oracle10g gibt es die neuen Datentypen BINARY_FLOAT und BINARY_DOUBLE (Doku). Sie speichern Fließkommazahlen im IEEE754-Format und dienen als Ergänzung zu NUMBER. BINARY_FLOAT und BINARY_DOUBLE haben zwei Vorteile: zum einen werden Fließkommazahlen wesentlich kompakter gespeichert und zum anderen werden Fließkommaoperationen wesentlich schneller durchgeführt. Grund für diesen Performancevorteil ist, dass die Operationen durch die Hardware (die CPU) unterstützt werden. Auf der anderen Seite steht allerdings auch ein Nachteil: BINARY_FLOAT und BINARY_DOUBLE speichern Fließkommazahlen im Binärformat (Basis 2), NUMBER speichert im Dezimalformat (Basis 10). Geht es also um dezimale Rundungen, so garantiert nur NUMBER die exakte Rundung ohne Fehler. Dazu folgendes Zitat aus der Doku: "For a decimal floating-point number format like Oracle NUMBER, rounding is done to the nearest decimal place (for example. 1000, 10, or 0.01). The IEEE 754 formats use a binary format for floating-point values and round numbers to the nearest binary place (for example: 1024, 512, or 1/64)."
SQL> select dump(to_number(sqrt(2)),16) from dual;

DUMP(TO_NUMBER(SQRT(2)),16)
------------------------------------------------------------------------
Typ=2 Len=21: c1,2,2a,2b,e,39,18,4a,a,33,31,51,11,59,49,2b,a,46,51,4f,3a

SQL> select dump(to_binary_double(sqrt(2)), 16) from dual;

DUMP(TO_BINARY_DOUBLE(SQRT(2)),16)
--------------------------------------
Typ=101 Len=8: bf,f6,a0,9e,66,7f,3b,cd
Man sieht deutlich, dass BINARY_DOUBLE den Wert (Quadratwurzel aus "2") in nur 8 Byte speichert, während NUMBER deren 21 benötigt. Das nachfolgende SQL*Plus-Skript zeigt auch den Performancevorteil. Wichtig ist, dass bei Funktionen wie SQRT, der Parameter (also die Zahl, aus der die Wurzel gezogen werden soll) vorher in BINARY_FLOAT bzw. BINARY_DOUBLE umgewandelt wird (Test 3) - ansonsten kann die Hardwareunterstützung nicht genutzt werden und die Performance wird sogar schlechter (Test 2).
set timing on
set verify off
set serveroutput on size 200000
set echo off

accept COUNTER default '1000000' prompt '>> Anzahl Iterationen [1000000]: '


prompt >> 1. Test mit NUMBER
prompt >>
prompt >> val := val + sqrt(i)
prompt >>

declare
  i     pls_integer;
  val   number := 0;
begin
  for i in 1..&COUNTER. loop
    val := val + sqrt(i);
  end loop;
  dbms_output.put_line('Value: '||val);
end;
/

prompt >> 2. Test mit BINARY_DOUBLE
prompt >>
prompt >> val := val + sqrt(i)
prompt >>

declare
  i     pls_integer;
  val   binary_double := 0d;
begin
  for i in 1..&COUNTER. loop
    val := val + sqrt(i);
  end loop;
  dbms_output.put_line('Value: '||val);
end;
/

prompt >> 3. Test mit BINARY_DOUBLE
prompt >>
prompt >> val := val + sqrt(to_binary_double(i))
prompt >>

declare
  i     pls_integer;
  val   binary_double := 0d;
begin
  for i in 1..&COUNTER. loop
    val := val + sqrt(to_binary_double(i));
  end loop;
  dbms_output.put_line('Value: '||val);
end;
/

Keine Kommentare:

Beliebte Postings