sql >> Databasteknik >  >> RDS >> Mysql

Bästa praxis för att lagra vikter i en SQL-databas?

Du hävdar att det finns inneboende felaktigheter i flyttal. Jag tycker att det här förtjänar att utforskas lite först.

När du bestämmer dig för ett siffersystem för att representera ett tal (antingen på ett papper, i en datorkrets eller någon annanstans), finns det två separata frågor att överväga:

  1. dess grund; och

  2. dess format .

Välj en bas, valfri bas...

Begränsat av ändligt utrymme kan man inte representera en godtycklig medlem av en oändlig uppsättning . Till exempel:oavsett hur mycket papper du köper eller hur liten din handstil, skulle det alltid vara möjligt att hitta ett heltal som inte passar i det givna utrymmet (du kan bara fortsätta lägga till extra siffror tills papperet tar slut). Så, med heltal , vi begränsar vanligtvis vårt ändliga utrymme till att endast representera de som faller inom något särskilt intervall - t.ex. om vi har utrymme för det positiva/negativa tecknet och tre siffror kan vi begränsa oss till intervallet [-999,+999] .

Varje icke-tomt intervall innehåller en oändlig uppsättning reella tal. Med andra ord, oavsett vilket intervall man tar över de reella talen – vare sig det är [-999,+999] , [0,1] , [0.000001,0.000002] eller något annat – det finns fortfarande en oändlig uppsättning reella värden inom det intervallet (man behöver bara fortsätta att lägga till (icke-noll) bråksiffror)! Därför måste godtyckliga reella tal alltid vara "avrundad" till något som kan representeras i ändligt utrymme.

Mängden reella tal som kan representeras i ändligt utrymme beror på det siffersystem som används. I vår (bekanta) positionella bas-10 system, ändligt utrymme kommer att räcka för en halv (>0.510 ) men inte för en tredjedel (0.33333…10 ); däremot i den (mindre bekanta) positionella bas-9 systemet är det tvärtom (samma siffror är respektive 0.44444…9 och 0.39 ). Konsekvensen av allt detta är att vissa tal som kan representeras med endast en liten mängd utrymme i positionsbas-10 (och därför uppträder att vara väldigt "rund" för oss människor), t.ex. en tiondel, skulle faktiskt kräva att oändliga binära kretsar lagras exakt (och därför inte verkar vara särskilt "runda" för våra digitala vänner)! Noterbart, eftersom 2 är en faktor på 10, är ​​detsamma inte sant omvänt:alla tal som kan representeras med ändlig binär kan också representeras med ändlig decimal.

Vi kan inte göra bättre för kontinuerliga kvantiteter. I slutändan måste sådana kvantiteter använda en finit representation i vissa siffersystem:det är godtyckligt om det systemet råkar vara lätt på datorkretsar, på mänskliga fingrar, på något annat eller på ingenting alls – vilket system som än används, värdet måste vara rundad och därför alltid resulterar i "representationsfel".

Med andra ord, även om man har ett helt korrekt mätinstrument (vilket är fysiskt omöjligt), så kommer alla mätningar som rapporteras redan att ha avrundats till ett tal som råkar passa på displayen (i vilken bas den än använder – vanligtvis decimal, av uppenbara skäl). Så "86.2 oz" är faktiskt aldrig "86.2 oz " utan snarare en representation av "något mellan 86.1500000... oz och 86.2499999... oz ". (Faktiskt, eftersom instrumentet i verkligheten är ofullkomligt, allt vi någonsin kan säga är att vi har några grad av förtroende att det faktiska värdet faller inom det intervallet – men det avviker definitivt på något sätt från punkten här).

Men vi kan göra det bättre för diskreta kvantiteter . Sådana värden är inte "godtyckliga reella tal" och därför gäller inget av ovanstående för dem:de kan representeras exakt i det siffersystem där de definierades – och faktiskt bör vara (eftersom omvandling till ett annat siffersystem och trunkering till en ändlig längd skulle resultera i avrundning till ett inexakt tal). Datorer kan (ineffektivt) hantera sådana situationer genom att representera numret som en sträng:t.ex. överväga ASCII eller BCD kodning.

Använd ett format...

Eftersom det är en egenskap av siffersystemets (något godtyckliga) grund, har om ett värde verkar vara "rundt" eller inte någon betydelse för dess precision . Det är en riktigt viktig observation , vilket strider mot många människors intuition (och det är anledningen till att jag tillbringade så mycket tid på att förklara numerisk grund ovan).

Precisionen bestäms istället av hur många signifikanta siffror en representation har . Vi behöver ett lagringsformat som kan registrera våra värden till minst så många betydande siffror som vi anser att de är korrekta . Med exempel på värden som vi anser vara korrekta när de anges som 86.2 och 0.0000862 , de två vanligaste alternativen är:

  • Fast punkt , där antalet signifikanta siffror beror på storleken :t.ex. i fast representation med 5 decimaler skulle våra värden lagras som 86.20000 och 0.00009 (och har därför 7 respektive 1 signifikanta precisionssiffror). I det här exemplet har precisionen gått förlorad i det senare värdet (och det skulle faktiskt inte ta mycket mer för att vi skulle ha varit helt oförmögna att representera något av betydelse); och det tidigare värdet lagrat falsk precision , vilket är ett slöseri med vårt ändliga utrymme (och det skulle faktiskt inte ta mycket mer för att värdet skulle bli så stort att det svämmar över lagringskapaciteten).

    Ett vanligt exempel på när detta format kan vara lämpligt är för ett redovisningssystem:penningbelopp måste vanligtvis spåras till en krona oberoende av deras storlek (därför krävs mindre precision för små värden och mer precision krävs för stora värden). Som det händer så anses valuta vanligtvis också vara diskret (pennies är odelbara), så detta är också ett bra exempel på en situation där en viss grund (decimal för de flesta moderna valutor) är önskvärd för att undvika representationsfelen som diskuterats ovan.

  • Flytande punkt , där antalet signifikanta siffror är konstant oavsett storlek :t.ex. i 5-signifikanta siffrors decimalrepresentation skulle våra värden lagras som 86.200 och 0.000086200 (och har per definition 5 signifikanta precisionssiffror båda gångerna). I det här exemplet har båda värdena lagrats utan någon förlust av precision; och de båda har också samma mängd av falsk precision, vilket är mindre slöseri (och vi kan därför använda vårt ändliga utrymme för att representera ett mycket större spektrum av värden – både stora och små).

    Ett vanligt exempel på när detta format kan vara lämpligt är för att registrera alla verkliga mätningar :precisionen hos mätinstrument (som alla lider av både systematisk och slumpmässigt fel) är ganska konstant oavsett skala så, givet tillräckligt signifikanta siffror (vanligtvis runt 3 eller 4 siffror), går absolut ingen precision förlorad även om en förändring av basen resulterade i avrundning till ett annat tal .

    Men hur exakta är lagringsformaten med flyttal används av våra datorer?

    • En IEEE754 single precision (binary32) flyttal nummer har 24 bitar, eller log10(2) (över 7) siffror, av betydelse—dvs. den har en tolerans på mindre än ±0.000006% . Det är med andra ord mer exakt än att säga "86.20000 ".

    • En IEEE754 dubbel precision (binary64) flyttal nummer har 53 bitar, eller log10(2) (nästan 16) siffror, av betydelse—d.v.s. den har en tolerans på drygt ±0.00000000000001% . Med andra ord är det mer exakt än att säga "86.2000000000000 ".

    Det viktigaste att inse är att dessa format är över tio tusen respektive och över en biljon gånger mer exakt än att säga "86.2" – även om exakta omvandlingar av binären tillbaka till decimal råkar innehålla felaktig falsk precision (som vi måste ignorera:mer om detta inom kort)!

Lägg också märke till att båda fixade och Flyttalsformat kommer att resultera i förlust av precision när ett värde är känt mer exakt än vad formatet stöder. Sådana avrundningsfel kan fortplanta sig i aritmetiska operationer för att ge uppenbarligen felaktiga resultat (vilket utan tvekan förklarar din hänvisning till de "inneboende felaktigheterna" i flyttal):till exempel 3 × 3000 i en fast punkt på 5 platser skulle ge 999.99000 istället för 1000.00000; och 7 − ⁄50 i 5-signifikanta siffror flyttal skulle ge 0.0028600 istället för 0.0028571 .

Området för numerisk analys är dedikerad till att förstå dessa effekter, men det är viktigt att inse att alla ett användbart system (även när du utför beräkningar i ditt huvud) är sårbart för sådana problem eftersom ingen beräkningsmetod som garanterat kommer att avslutas någonsin kan erbjuda oändlig precision :tänk till exempel på hur man beräknar arean av en cirkel – det kommer nödvändigtvis att bli en förlust av precision i värdet som används för π, vilket kommer att fortplantas till resultatet.

Slutsats

  1. Mätningar i den verkliga världen bör använda binär flyttal :det är snabbt, kompakt, extremt exakt och inte värre än något annat (inklusive decimalversionen som du startade från). Sedan MySQL:s flyttalsdatatyper är IEEE754, det är precis vad de erbjuder.

  2. Valutaapplikationer bör använda denary fix point :även om det är långsamt och slösar med minne, säkerställer det både att värden inte avrundas till inexakta kvantiteter och att pengar inte går förlorade på stora penningbelopp. Sedan MySQL:s fixpunktsdatatyper är BCD-kodade strängar, det är precis vad de erbjuder.

Slutligen, kom ihåg att programmeringsspråk vanligtvis representerar bråkvärden med binär flyttal typer:så om din databas lagrar värden i ett annat format måste du vara försiktig med hur de förs in i din applikation, annars kan de konverteras (med alla de problem som det medför) i gränssnittet.

Vilket alternativ är bäst i det här fallet?

Förhoppningsvis har jag övertygat dig om att dina värderingar kan säkert (och bör). ) lagras i flyttalstyper utan att oroa dig för mycket över eventuella "felaktigheter"? Kom ihåg att de är fler exakt än din tunna 3-signifikantsiffriga decimalrepresentation någonsin var:du måste bara ignorera falsk precision (men man måste alltid gör det ändå, även om du använder ett decimalformat med fast punkt).

När det gäller din fråga:välj antingen alternativ 1 eller 2 framför alternativ 3 – det gör jämförelser lättare (för att till exempel hitta den maximala massan kan man bara använda MAX(mass) , medan att göra det effektivt över två kolumner skulle kräva en del kapsling).

Mellan dessa två spelar det ingen roll vilken som väljer – flyttalstal lagras med ett konstant antal signifikanta bitar oavsett deras skala .

Dessutom, medan det i det allmänna fallet kan hända att vissa värden avrundas till binära tal som är närmare deras ursprungliga decimalrepresentation med alternativ 1, medan andra samtidigt avrundas till binära tal som är närmare deras ursprungliga decimalrepresentation med alternativ 2, som vi kommer inom kort att se sådana representationsfel endast uppenbara sig inom den falska precision som alltid bör ignoreras.

Men i denna eftersom det händer att det finns 16 uns till 1 pund (och 16 är en potens av 2), är de relativa skillnaderna mellan ursprungliga decimalvärden och lagrade binära tal med de två metoderna identiska :

  1. 5.387510 (inte 5.3367187510 som anges i din fråga) skulle lagras i en binary32-float som 101.0110001100110011001102 (vilket är 5.3874998092651367187510 ):detta är 0.0000036% från det ursprungliga värdet (men, som diskuterats ovan, var "originalvärdet" redan en ganska usel representation av den fysiska kvantitet det representerar).

    Eftersom vi vet att en binary32-float endast lagrar 7 decimalsiffror med precision vet vår kompilator med säkerhet att allt från 8:e siffran och framåt är definitivt falsk precision och därför måste ignoreras i varje fall – alltså förutsatt att vårt indatavärde inte krävde mer precision än så (och om det gjorde det, var binary32 uppenbarligen fel val av format), detta garantier en återgång till ett decimalvärde som ser lika runt som det vi startade från:5.38750010 . Men vi borde verkligen tillämpa domänkunskap vid denna tidpunkt (som vi borde med vilket lagringsformat som helst) för att kassera ytterligare falsk precision som kan finnas, såsom de två efterföljande nollorna.

  2. 86.210 skulle lagras i en binary32-float som 1010110.001100110011001102 (vilket är 86.199996948242187510 ):detta är också 0.0000036% från det ursprungliga värdet. Som tidigare ignorerar vi sedan falsk precision för att återgå till vår ursprungliga inmatning.

Lägg märke till hur de binära representationerna av talen är identiska, förutom placeringen av radixpunkten (vilket är fyra bitar ifrån varandra):

101.0110 00110011001100110
101 0110.00110011001100110

Detta beror på att 5,3875 × 2 =86,2.



  1. SIN() Exempel i SQL Server

  2. Postgres INTERVAL med värde från tabell

  3. PostgreSQL-säkerhetskopieringsmetodfunktioner i AWS S3

  4. Använder du .aggregate() på ett värde som introducerats med .extra(select={...}) i en Django-fråga?