Risposte:
Questo è quello che ho scoperto quando avevo questo dubbio.
mysql> create table numbers (a decimal(10,2), b float);
mysql> insert into numbers values (100, 100);
mysql> select @a := (a/3), @b := (b/3), @a * 3, @b * 3 from numbers \G
*************************** 1. row ***************************
@a := (a/3): 33.333333333
@b := (b/3): 33.333333333333
@a + @a + @a: 99.999999999000000000000000000000
@b + @b + @b: 100
Il decimale ha fatto esattamente ciò che dovrebbe fare in questi casi, ha troncato il resto, perdendo così la parte 1/3.
Quindi per le somme il decimale è migliore, ma per le divisioni il float è migliore, fino a un certo punto, ovviamente. Voglio dire, l'uso di DECIMAL non ti darà "un'aritmetica a prova di errore" in alcun modo.
Spero che questo ti aiuti.
@aDare 99.999999999000000000000000000000 non è il DECIMAL? Che è tecnicamente corretto.
Un "float" nella maggior parte degli ambienti è un tipo binario a virgola mobile. Può memorizzare con precisione i valori di base 2 (fino a un certo punto), ma non può memorizzare con precisione molti valori di base 10 (decimali). I galleggianti sono i più appropriati per i calcoli scientifici. Stanno Non appropriato per la maggior parte la matematica business-oriented, e l'uso inadeguato di carri ti morderà. Molti valori decimali non possono essere rappresentati esattamente in base-2. 0.1non è possibile, ad esempio, e quindi vedrai strani risultati come 1.0 - 0.1 = 0.8999999.
I decimali memorizzano i numeri di base 10. Il decimale è un buon tipo per la maggior parte della matematica aziendale (ma qualsiasi tipo di "denaro" incorporato è più appropriato per i calcoli finanziari), dove l'intervallo di valori supera quello fornito dai tipi interi e sono necessari valori frazionari. I decimali, come suggerisce il nome, sono progettati per numeri in base 10: possono memorizzare accuratamente i valori decimali (di nuovo, fino a un certo punto).
MySQL ha recentemente cambiato il modo in cui memorizzano il tipo DECIMAL . In passato memorizzavano i caratteri (o i nybbles) per ogni cifra comprendente una rappresentazione ASCII (o nybble) di un numero - vs - un intero di complemento a due, o qualche derivata di esso.
Il formato di archiviazione corrente per DECIMAL è una serie di numeri interi 1,2,3 o 4 byte i cui bit sono concatenati per creare un numero di complemento a due con un punto decimale implicito, definito dall'utente, e memorizzato nello schema DB quando si dichiara la colonna e specificarne la dimensione DECIMALE e la posizione del punto decimale.
A titolo di esempio, se si prende un int a 32 bit è possibile memorizzare qualsiasi numero compreso tra 0 e 4.294.967.295. Questo coprirà in modo affidabile solo 999.999.999, quindi se buttassi fuori 2 bit e usassi (1 << 30 -1) non rinunceresti a nulla. Coprire tutti i numeri di 9 cifre con solo 4 byte è più efficiente rispetto a coprire 4 cifre in 32 bit utilizzando 4 caratteri ASCII o 8 cifre di nybble. (un nybble è di 4 bit, consentendo valori 0-15, più di quanto è necessario per 0-9, ma non è possibile eliminare tale spreco andando a 3 bit, perché copre solo i valori 0-7)
L'esempio usato nei documenti online di MySQL usa DECIMAL (18,9) come esempio. Sono 9 cifre prima e 9 cifre dietro il punto decimale implicito, che come spiegato sopra richiede la seguente memorizzazione.
Come 18 caratteri a 8 bit: 144 bit
Come 18 nybbles a 4 bit: 72 bit
Come 2 numeri interi a 32 bit: 64 bit
Attualmente DECIMAL supporta un massimo di 65 cifre, come DECIMAL (M, D) dove il valore più grande per M consentito è 65 e il valore più grande di D consentito è 30.
Per non richiedere blocchi di 9 cifre alla volta, vengono utilizzati numeri interi inferiori a 32 bit per aggiungere cifre utilizzando numeri interi da 1,2 e 3 byte. Per qualche ragione che sfida la logica, sono stati usati invece di ints non firmati, e in tal modo viene eliminato 1 bit, ottenendo le seguenti capacità di archiviazione. Per 1,2 e 4 byte il bit perso non ha importanza, ma per il 3 byte int è un disastro perché un'intera cifra viene persa a causa della perdita di quel singolo bit.
Con un int a 7 bit: 0 - 99
Con un int di 15 bit: 0 - 9.999
Con un int a 23 bit: 0 - 999.999 (0 - 9.999.999 con un int a 24 bit)
Gli interi 1,2,3 e 4 byte sono concatenati insieme per formare un "pool di bit" che DECIMAL utilizza per rappresentare il numero esattamente come un intero di complemento a due. Il punto decimale NON è memorizzato, è implicito.
Ciò significa che il motore DB non richiede conversioni da ASCII a int per convertire il "numero" in qualcosa che la CPU riconosce come numero. Nessun arrotondamento, nessun errore di conversione, è un numero reale che la CPU può manipolare.
I calcoli su questo numero intero arbitrariamente grande devono essere eseguiti nel software, poiché non esiste alcun supporto hardware per questo tipo di numero, ma queste librerie sono molto vecchie e altamente ottimizzate, essendo state scritte 50 anni fa per supportare i dati a virgola mobile di precisione arbitraria IBM 370 Fortran . Sono ancora molto più lenti dell'algebra di interi di dimensioni fisse eseguita con hardware intero CPU o calcoli in virgola mobile eseguiti sulla FPU.
In termini di efficienza di archiviazione, poiché l'esponente di un float è collegato a ogni singolo float, specificando implicitamente dove si trova il punto decimale, è enormemente ridondante e quindi inefficiente per il lavoro del DB. In un DB sai già dove il punto decimale deve andare in primo piano e ogni riga nella tabella che ha un valore per una colonna DECIMAL deve solo guardare la specifica 1 e unica di dove deve essere posizionato, memorizzato quel punto decimale nello schema come argomenti per un DECIMAL (M, D) come l'implicazione dei valori M e D.
Le molte osservazioni qui trovate su quale formato deve essere utilizzato per vari tipi di applicazioni sono corrette, quindi non voglio affermare il punto. Mi sono preso il tempo di scriverlo qui perché chiunque stia mantenendo la documentazione online di MySQL collegata non capisce nulla di quanto sopra e dopo cicli di tentativi sempre più frustranti di spiegarglielo ho rinunciato. Una buona indicazione di quanto male abbiano capito cosa stavano scrivendo è la presentazione molto confusa e quasi indecifrabile dell'argomento.
Come ultimo pensiero, se hai bisogno di calcoli in virgola mobile ad alta precisione, negli ultimi 20 anni ci sono stati enormi progressi nel codice in virgola mobile e il supporto hardware per il float a 96 bit e Quadruple Precision è dietro l'angolo, ma ci sono buone librerie arbitrarie di precisione là fuori se la manipolazione del valore memorizzato è importante.
Non solo specifico per MySQL, la differenza tra tipi float e decimali è il modo in cui rappresentano i valori frazionari. I tipi a virgola mobile rappresentano le frazioni in binario, che possono rappresentare solo valori come {m*2^n | m, n Integers}. valori come 1/5 non possono essere rappresentati con precisione (senza errore di arrotondamento). I numeri decimali sono similmente limitati, ma rappresentano numeri simili {m*10^n | m, n Integers}. I decimali non possono ancora rappresentare numeri come 1/3, ma spesso accade in molti campi comuni, come la finanza, che l'aspettativa è che certe frazioni decimali possano sempre essere espresse senza perdita di fedeltà. Poiché un numero decimale può rappresentare un valore come $0.20(un quinto di un dollaro), è preferito in quelle situazioni.
decimale è per quantità fisse come denaro dove si desidera un numero specifico di cifre decimali. I galleggianti servono per memorizzare ... numeri di precisione in virgola mobile.
Ho trovato questo utile:
In genere, i valori Float sono validi per i calcoli scientifici, ma non devono essere utilizzati per i valori finanziari / monetari. Per la matematica orientata al business, utilizzare sempre decimale.
Fonte: http://code.rohitink.com/2013/06/12/mysql-integer-float-decimal-data-types-differences/
mysql> CREATE TABLE num(id int ,fl float,dc dec(5,2));
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO num VALUES(1,13.75,13.75);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO num VALUES(2,13.15,13.15);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM num WHERE fl = 13.15;
Empty set (0.00 sec)
mysql> SELECT * FROM num WHERE dc = 13.15;
+------+-------+-------+
| id | fl | dc |
+------+-------+-------+
| 2 | 13.15 | 13.15 |
+------+-------+-------+
1 row in set (0.00 sec)
mysql> SELECT SUM(fl) ,SUM(dc) FROM num;
+--------------------+---------+
| SUM(fl) | SUM(dc) |
+--------------------+---------+
| 26.899999618530273 | 26.90 |
+--------------------+---------+
1 row in set (0.00 sec)
mysql> SELECT * FROM num WHERE ABS(fl - 13.15)<0.01;
+------+-------+-------+
| id | fl | dc |
+------+-------+-------+
| 2 | 13.15 | 13.15 |
+------+-------+-------+
1 row in set (0.00 sec)
Tipi a virgola mobile (valore approssimativo) - FLOAT, DOUBLE
I tipi FLOAT e DOUBLE rappresentano valori numerici approssimativi dei dati. MySQL utilizza quattro byte per i valori a precisione singola e otto byte per i valori a precisione doppia.
Per FLOAT, lo standard SQL consente una specifica facoltativa della precisione (ma non l'intervallo dell'esponente) in bit seguendo la parola chiave FLOAT tra parentesi. MySQL supporta anche questa specifica di precisione opzionale, ma il valore di precisione viene utilizzato solo per determinare le dimensioni di archiviazione. Una precisione da 0 a 23 produce una colonna FLOAT a precisione singola a 4 byte. Una precisione da 24 a 53 si traduce in una colonna DOUBLE a doppia precisione da 8 byte.
MySQL consente una sintassi non standard: FLOAT (M, D) o REAL (M, D) o DOUBLE PRECISION (M, D). Qui, "(M, D)" significa che i valori possono essere memorizzati con un massimo di M cifre in totale, di cui le cifre D possono essere dopo il punto decimale. Ad esempio, una colonna definita come FLOAT (7,4) apparirà come -999.9999 quando visualizzata. MySQL esegue l'arrotondamento durante la memorizzazione dei valori, quindi se si inserisce 999.00009 in una colonna FLOAT (7,4), il risultato approssimativo è 999.0001.
Poiché i valori in virgola mobile sono approssimativi e non memorizzati come valori esatti, i tentativi di trattarli come esatti nei confronti possono causare problemi. Sono inoltre soggetti a dipendenze dalla piattaforma o dall'implementazione.
Per la massima portabilità, il codice che richiede la memorizzazione di valori numerici approssimativi dei dati dovrebbe usare FLOAT o DOUBLE PRECISION senza specifiche di precisione o numero di cifre.
https://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
Problemi con valori a virgola mobile
I numeri in virgola mobile a volte causano confusione perché sono approssimativi e non memorizzati come valori esatti . Un valore in virgola mobile come scritto in un'istruzione SQL potrebbe non essere uguale al valore rappresentato internamente. I tentativi di trattare i valori in virgola mobile come esatti nei confronti possono portare a problemi. Sono inoltre soggetti a dipendenze dalla piattaforma o dall'implementazione. I tipi di dati FLOAT e DOUBLE sono soggetti a questi problemi. Per le colonne DECIMAL, MySQL esegue le operazioni con una precisione di 65 cifre decimali, che dovrebbe risolvere i problemi di imprecisione più comuni.
L'esempio seguente utilizza DOUBLE per dimostrare come i calcoli eseguiti utilizzando operazioni in virgola mobile sono soggetti a errori in virgola mobile.
mysql> CREATE TABLE t1 (i INT, d1 DOUBLE, d2 DOUBLE);
mysql> INSERT INTO t1 VALUES (1, 101.40, 21.40), (1, -80.00, 0.00),
-> (2, 0.00, 0.00), (2, -13.20, 0.00), (2, 59.60, 46.40),
-> (2, 30.40, 30.40), (3, 37.00, 7.40), (3, -29.60, 0.00),
-> (4, 60.00, 15.40), (4, -10.60, 0.00), (4, -34.00, 0.00),
-> (5, 33.00, 0.00), (5, -25.80, 0.00), (5, 0.00, 7.20),
-> (6, 0.00, 0.00), (6, -51.40, 0.00);
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b
-> FROM t1 GROUP BY i HAVING a <> b;
+------+-------+------+
| i | a | b |
+------+-------+------+
| 1 | 21.4 | 21.4 |
| 2 | 76.8 | 76.8 |
| 3 | 7.4 | 7.4 |
| 4 | 15.4 | 15.4 |
| 5 | 7.2 | 7.2 |
| 6 | -51.4 | 0 |
+------+-------+------+
Il risultato è corretto Anche se i primi cinque record sembrano non soddisfare il confronto (i valori di aeb non sembrano essere diversi), possono farlo perché la differenza tra i numeri appare intorno al decimo decimale o giù di lì, a seconda dei fattori come l'architettura del computer o la versione del compilatore o il livello di ottimizzazione. Ad esempio, diverse CPU possono valutare i numeri in virgola mobile in modo diverso.
Se le colonne d1 e d2 fossero state definite come DECIMAL anziché DOUBLE, il risultato della query SELECT avrebbe contenuto solo una riga, l'ultima mostrata sopra.
Il modo corretto di fare il confronto di numeri in virgola mobile è prima di decidere una tolleranza accettabile per le differenze tra i numeri e quindi fare il confronto con il valore di tolleranza. Ad esempio, se concordiamo che i numeri in virgola mobile dovrebbero essere considerati uguali se sono uguali entro una precisione di uno su diecimila (0,0001), il confronto dovrebbe essere scritto per trovare differenze maggiori del valore di tolleranza:
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
-> GROUP BY i HAVING ABS(a - b) > 0.0001;
+------+-------+------+
| i | a | b |
+------+-------+------+
| 6 | -51.4 | 0 |
+------+-------+------+
1 row in set (0.00 sec)
Al contrario, per ottenere righe in cui i numeri sono uguali, il test dovrebbe trovare differenze all'interno del valore di tolleranza:
mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
-> GROUP BY i HAVING ABS(a - b) <= 0.0001;
+------+------+------+
| i | a | b |
+------+------+------+
| 1 | 21.4 | 21.4 |
| 2 | 76.8 | 76.8 |
| 3 | 7.4 | 7.4 |
| 4 | 15.4 | 15.4 |
| 5 | 7.2 | 7.2 |
+------+------+------+
5 rows in set (0.03 sec)
I valori in virgola mobile sono soggetti alle dipendenze della piattaforma o dell'implementazione. Supponiamo di eseguire le seguenti istruzioni:
CREATE TABLE t1(c1 FLOAT(53,0), c2 FLOAT(53,0));
INSERT INTO t1 VALUES('1e+52','-1e+52');
SELECT * FROM t1;
Su alcune piattaforme, l'istruzione SELECT restituisce inf e -inf. Su altri, restituisce 0 e -0.
Un'implicazione dei problemi precedenti è che se si tenta di creare uno slave di replica scaricando il contenuto della tabella con mysqldump sul master e ricaricando il file di dump nello slave, le tabelle contenenti colonne in virgola mobile potrebbero differire tra i due host.
https://dev.mysql.com/doc/refman/5.5/en/problems-with-float.html
Regola dura e veloce
Se tutto ciò che devi fare è aggiungere, sottrarre o moltiplicare i numeri che stai memorizzando, DECIMAL è la cosa migliore.
Se hai bisogno di dividere o fare qualsiasi altra forma di aritmetica o algebra sui dati, quasi sicuramente sarai più felice con il float. Le librerie a virgola mobile e sui processori Intel, lo stesso processore a virgola mobile, hanno tonnellate di operazioni per correggere, sistemare, rilevare e gestire la tormenta delle eccezioni che si verificano quando si svolgono le tipiche funzioni matematiche, in particolare le funzioni trascendentali.
Per quanto riguarda l'accuratezza, una volta ho scritto un sistema di budget che calcolava il contributo% di ciascuno di oltre 3.000 account, per 3.600 unità di budget, per mese al nodo di consolidamento di quell'unità, quindi basato su quella matrice di percentuali (3.000 + x 12 x 3.600) Ho moltiplicato gli importi preventivati dai nodi organizzativi più alti fino ai successivi 3 livelli dei nodi organizzativi, e da quel momento ho calcolato tutti i valori (3.000 + 12) per tutte le 3.200 unità di dettaglio. Milioni e milioni e milioni di calcoli in virgola mobile a precisione doppia, ognuno dei quali eliminerebbe il roll-up di tutte quelle proiezioni in un consolidamento bottom-up al livello più alto dell'organizzazione.
L'errore totale in virgola mobile dopo tutti questi calcoli era ZERO . Era il 1986 e oggi le librerie in virgola mobile sono molto, molto meglio di quanto non fossero allora. Intel esegue tutti i suoi calcoli intermedi dei doppi con precisione a 80 bit, il che elimina quasi tutti gli errori di arrotondamento. Quando qualcuno ti dice "è un errore in virgola mobile" è quasi certo NON vero.
float(e double) rappresenta le frazioni binarie
decimal rappresenta le frazioni decimali
declare @float as float(10)
declare @Decimal as decimal(10)
declare @Inetger as int
set @float =10.7
set @Decimal =10.7
set @Inetger=@Decimal
print @Inetger
in float quando impostato su integer, stampa 10 ma in decimale 11
FLOAT(m,n), porta a due arrotondamenti; nel frattempo, non fornisce nulla di utile.