Articles

kelluvien tai todellisten datatyyppien käytön vaaroista

on vanha vitsi liukupistearitmetiikasta:

”Jos leikkaan kakun kolmeen, jokainen pala on 0,33 kakkua. Jos pistän kaikki kolme palaa yhteen, saan 0,99 kakkua. Minne loput kakusta on kadonnut?”
– ” yksinkertainen. That ’s the little bit stuck on the knife”

Liukulukuaritmetiikassa on kyse approksimaation sietämisestä ja hallinnasta, jotta vältettäisiin ylivirtausvirheet laskelmissa. Reaalimaailmassa välitämme yleensä numeroiden täsmällisyydestä ja uhraamme sen sijaan tilaa ja resursseja välttääksemme ylivuodon.

siinä missä tiede toimii onnellisesti virhemarginaalin sisällä, liikekirjanpidossa tarkkuus ratkaisee. Kun olin cub ohjelmoija, kirjoitin kerran mitä ajattelin olevan täysin sopiva tapa laskea voittoa pörssimeklari tarjouksia. Miljoonassa punnassa se oli korkeintaan penni tai pari. Olin tyytyväinen. Se käytti Pl/1-kääntäjän luontaisia laskelmia, joita käytimme tuolloin rahoituspakettien kehittämiseen. Näytin heille hienosti muotoillun sovelluksen, ja he olivat kauhuissaan. Penni miljoonasta punnasta tuntui kovapintaisista kaupunkikauppiaista holtittomalta. He eivät suostuneet. Minun oli pakko kirjoittaa binäärikoodattu desimaali (BCD) – paketti assembler-koodilla, joka oli täsmälleen tarkka.

SQL Promptilla on koodianalyysisääntö (bp023), joka varoittaa FLOAT tai REAL tietotyypit, johtuen merkittävistä epätarkkuuksista, joita ne voivat tuoda esille sellaisiin laskelmiin, joita monet organisaatiot rutiininomaisesti suorittavat SQL Server-tiedoillaan.

likimääräiset numerotietotyypit

Liukulukuaritmetiikka laadittiin aikana, jolloin ensisijaisena tavoitteena oli säästää muistia ja antaa samalla monipuolinen tapa tehdä laskutoimituksia, joissa oli mukana suuria lukuja. Vaikka se on edelleen hyödyllinen monenlaisissa tieteellisissä laskelmissa, erityisesti niissä, jotka ovat liukulukujen aritmetiikan kaksitarkkuusstandardin IEEE 754 mukaisia, se on pakostakin kompromissi. Johtolanka on tämän tyyppisten tietojen ja aritmetiikan nimissä: ’approximate’. Liukulukuluvut eivät voi kuvata tarkasti kaikkia reaalilukuja: lisäksi liukulukuoperaatiot eivät voi kuvata tarkasti kaikkia aritmeettisia operaatioita. Niiden hallussa olevan luvun suuruusalue on kuitenkin paljon suurempi kuin muilla numeerisilla tyypeillä on mahdollista, vaikka sitä ei aina pidettäisikään tarkasti.

liukulukulaskennan käyttöön liittyvät ongelmat johtuvat mutkikkaiden laskutoimitusten pyörähdyksistä, ja useimmiten ne nähdään, jos tieto on ”huonosti ehdollistettua”, jolloin syötteen pienet muutokset suurenevat ulostulossa. Epätarkkuudet ovat paljon vähemmän ilmeisiä lisäämällä tarkkuutta edustus numerot, mutta ne ovat edelleen läsnä, kuitenkin. On olemassa myös joitakin esoteerisia rajoituksia sellaisten lukujen käyttöön, jotka ovat voimassa mutta joita ei voida esittää liukuluvussa, kuten tan(π/2), mutta nämä todennäköisesti kiihottavat vain matemaatikkoja.

SQL Server liukulukutyypit

SQL-standardissa on kolme liukulukutyyppiä, likimääräisiä tietotyyppejä, REALDOUBLEPRECISION ja FLOAT(n). SQL Server noudattaa tätä paitsi sillä ei ole DOUBLEPRECISION datatyyppi, käyttäen FLOAT(53) sen sijaan. FLOAT(24) ja FLOAT(53) tietotyypit vastaavat IEEE 754-standardin Binary32: ta (yksi) ja Binary64: ää (kaksinkertainen), ja ne tallennetaan 4 ja 8 tavussa sekä 7 ja 16 numerossa. Ne ovat hyödyllisiä, kun on tärkeää, että laskelmat tuottavat saman tuloksen kuin IEEE 754: ää käyttävä.NET framework-sovellus. Kaksinkertaista tarkkuustyyppiä tarvitaan myös silloin, kun numerot ylittävät suuruudeltaan DECIMAL datatyypin (38 numeroa) salliman enimmäismäärän, tosin tarkkuushäviöllä. Likimääräisiä lukuja ei tietenkään voida luotettavasti käyttää missään tasa-arvotestissä, kuten WHERE lauseke.

laskelmat käyttäen todellista datatyyppiä (yksittäinen tarkkuus)

kokeilen REAL datatyyppi. FLOAT(24) datatyyppi eli pienempi reagoi samalla tavalla. Ensimmäinen asia muistaa, kun kokeillaan liukulukuja SQL Server on, että SSMS tekee liukulukuluku tavalla, joka peittää pieniä eroja. Esimerkiksi:

1
SELECT Convert(REAL,0.10000001490116119384765625)

…antaa 0.1

nähdäksesi tarkemmin, mitä arvoa liukulukuun tallennetaan, sinun on käytettävä Str () – funktiota, joka määrittää haluamasi tarkkuuden.

1
2
3
5

/*I ’ m being a little unfeil tässä, koska desimaaliluku 0.1 ei ole edustettavissa
liukulukuna; eksaktissa binääriesityksessä olisi ”1100”-sekvenssi
jatkuva loputtomasti:*/
declar @firstapproksimate real = 0.1
select str(@firstapproksimate,20,16) –pitäisi olla 0.100000001490116119384765625

jo nyt tämä on hälyttävää. Loppujen lopuksi käsittelemme tietoja, joissa on miljoonia rivejä, joten pieniä virheitä kasaantuu, elleivät pankkiirit pyöristysten tavoin laske niitä keskimäärin ulos. Tämä virhe on jo lähellä ”penni miljoonassa punnassa” (1/ 240000000), jonka mainitsin johdannossa!

välttäkäämme 0,1 ja laskekaamme se leijuvan pisteen kummajaiseksi. Entä jako 1: 3: een? Eihän tämä voi olla ongelma?

1
2
3
4
5

– SIITÄ, @payoffs REAL = 1
JOSSA todetaan @TODELLINEN tekijä =3
VALITSE Str(@payoffs /@jakaja,20,16) kuten osamäärä
–tuottaa 0.3333333432674408
– pitäisi olla 0.3333333333333333

Hups. Se käsitti väärin. Se on pieni virhe, mutta muista tarinani pankkiireista. Vastaus on joko oikein tai väärin, harmaapukuisille miehille ei löydy harmaan sävyjä. Kauppakorkeakoulussa on vain rasti ja risti. Ei merkkiä, joka tarkoittaa ”tarpeeksi lähellä”.

yksinkertainen testi on jakaa yksi numeroilla yhdestä kahteenkymmeneen. Mikä voisi mennä vikaan?

voimme tallentaa liukulukulaskennan ja numeerisen laskennan tulokset, jotka molemmat muunnetaan merkkijonoiksi ja sitten vertaamme merkkijonoja (varoitetaan, että STR() voi laittaa johtavaan tilaan, joka aiheuttaa komplikaation).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

DECLARE @divisor REAL, @dividend REAL = 1
DECLARE @FloatingPointCalculations TABLE (Divisor INT, QuotientFloatingPoint VARCHAR(20), QuotientNumeric VARCHAR(20));
DECLARE @ii INT = 1;
DECLARE @iiMax INT = 20;
WHILE (@ii <= @iiMax)
BEGIN
SELECT @divisor = @ii;
INSERT INTO @FloatingPointCalculations (Divisor, QuotientFloatingPoint,
QuotientNumeric)
SELECT @ii AS divisor, Str(@Dividend / @divisor, 20, 16) AS QuotientFloatingPoint,
Convert(VARCHAR(20), 1.0000000 / @ii) AS QuotientNumeric;
SELECT @ii += 1;
END;
SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations AS The;

Now, what if we list the rows where the numbers don’t match?

1
2
3

SELECT The.Divisor, The.QuotientFloatingPoint, The.QuotientNumeric
FROM @FloatingPointCalculations The
WHERE Left(LTrim(The.QuotientFloatingPoint),16)<> Left(LTrim(The.QuotientNumeric), 16)

Ugh! Vain jos jakaja oli 1, 2, 4, 8 tai 16 oli olemassa oikea tulos.

jos toivot, että kellunta oli jotenkin tarkka, eikä numeerinen versio ollut, tässä on Excelissä laskettu numeerinen osamäärä:

laskelmat käyttäen LIUKULUKUA(25) tai yli (kaksinkertainen tarkkuus)

Jos käytät kaksinkertaista tarkkuutta liukulukua, FLOAT(25) tai yli, testit läpäistään, koska STR() funktio sallii enintään kuusitoista paikkaa desimaalipilkun oikealla puolella. Jos niitä on enemmän kuin 16, Tulos typistetään. Kaksinkertainen tarkkuus datatype on kuusitoista numeroa, kun taas yksi tarkkuus datatype on seitsemän. Olet myös nähnyt, että yksittäinen tarkkuus datatyyppi saa seitsemän ensimmäistä numeroa oikein. Samoin tuplatarkkuus saa kuusitoista ensimmäistä numeroa oikein. Voimme vain laajentaa lukumäärää nähdäksemme likiarvon.

1
2

DECLAR @Firstapproksimate FLOAT(53) = 10000000000000000000. 1
select str(@firstapproksimate,40,16) as bignumberwithadecimal

tuo murto-osa on kadonnut, eikö niin? Ero lienee vain pieni, mutta joissain laskelmissa se voi aiheuttaa ongelmia.

johtopäätös

Liukulukuaritmetiikka on varastoinnissa nopeaa ja taloudellista, mutta antaa likimääräisen tuloksen. Se soveltuu hyvin ehdollistettuihin tieteellisiin sovelluksiin, mutta ei taloudellisiin laskelmiin, jotka edellyttävät, että jokin luku on joko ”oikea” tai ”väärä”. Sillä on myös ylimääräinen haitta tietokannassa, koska et voi luotettavasti ja johdonmukaisesti testata kahta likimääräistä numeroa tasa-arvolle.

ei ole oikein sanoa, että SQL-tietotyypeissä tai aritmetiikassa ei pitäisi koskaan käyttää liukulukuja. Likimääräisiä tyyppejä on SQL-standardissa tiettyä tarkoitusta varten. Haluaisin nykyään aina kiinni kaksinkertainen tarkkuus liukulukutyyppi SQL Server, jossa on sopiva vaatimus. Ne soveltuvat erinomaisesti esimerkiksi sääjärjestelmien mallintamiseen tai lentoratojen piirtämiseen, mutta eivät sellaisiin laskelmiin, joihin keskivertoorganisaatio todennäköisesti käyttää tietokantaa.

Jos havaitset näiden tyyppien virheellistä käyttöä, kannattaa vaihtaa sen sijaan sopivaan DECIMALNUMERIC tyyppiin. Jos tiedät tarvitsevasi liukulukulaskentaa ja osaat selittää miksi, niin luultavasti tiedät tarpeeksi välttääksesi liukulukulaskun sudenkuopat, kuten se, joka tapahtui kuuluisassa Patriot-ohjuksen epäonnistumisessa, joka johti suoraan 28 kuolemaan.