SQL Server - Quel type de données choisir Float ou Decimal ?

Il arrive assez souvent, lors de la conception d'une base de données, que certains hésitent sur le choix du type de données d'une colonne. Ils se posent la question, tout à fait légitime : « Dois-je choisir le type Float ou le type Decimal ? » (pour ne citer que ces 2 types de données numériques). 
Il est vrai que la réponse n'est pas évidente et fait même parfois l'objet de discussions interminables sur les forum ! 
Cet article se propose de vous aider ou du moins de vous éclairer et vous permettre de faire un choix, en toute connaissance de cause.

Commençons d'abord par regarder de plus près le type Float. La documentation officielle nous dit ceci :
Float : « Types de données approximatives à utiliser avec des données numériques à virgule flottante. Les données à virgule flottante sont approximatives ; il n'est donc pas possible de représenter précisément toutes les valeurs de ce type de données ».  

Une confusion assez répondu concerne l'acception du caractère approximatif du type Float. Beaucoup l’interprètent au sens « non précis » et disent « Le type Float n'est pas assez précis ».

En fait le caractère approximatif du type Float concerne la représentation interne physique, en mémoire d'une valeur de type Float selon la norme IEEE 754.
Le domaine (l'ensemble des valeurs possibles) du type Float peut être comparé à une suite de points discontinus, alignés sur une droite. Lorsqu'une valeur est située entre 2 points, elle est représentée par le point le plus proche, voilà ce qui explique pourquoi, pour le type Float, on parle de type données approximatives. 

Ci-dessous un exemple où le type Float s'avère, dans les calculs intermédiaires, plus précis que le type Decimal !
DECLARE @float1 float, 
        @float2 float, 
        @float3 float,
        @float4 float;

SET @float1 = 145;
SET @float2 = 0.07;
SET @float3 = @float1/@float2;
SELECT (@float3 / @float1) * @float2  AS "Doit être égale à 1";
Résultat :
Doit être égale à 1
1
Le même exemple, mais en utilisant le type Decimal :
DECLARE @decimal1  decimal(12,4),
        @decimal2  decimal(12,4),
        @decimal3  decimal(12,4),
        @decimal4  decimal(12,4); 

SET @decimal1 = 145;
SET @decimal2 = 0.07;
SET @decimal3 = @decimal1/@decimal2;

SELECT (@decimal3 / @decimal1) * @decimal2  AS "Doit être égale à 1";

Résultat :
Doit être égale à 1
1.00000001379310345
On voit bien que sur le plan mathématique, le résultat ci-dessus est faux. il devrait être égale à 1.
Au travers cet exemple, je ne veux surtout pas dire qu'il faut utiliser le type Float au lieu de Decimal, ni l’inverse. Chacun des 2 types a son utilité. le type Float peut être un bon choix pour les calculs scientifiques puisqu'il permet des mesures aussi proches de la réalité (plus précis !). Le type Decimal peut être approprié pour les chiffres ayant un nombre fixe de décimals tels que les montants exprimés en devise.

Transformer un type Float en Decimal est légitime et cela peut être un bon choix en rapport avec l’utilisation que vous en faites, mais attention, ce n'est pas transparent pour votre application. Je veux dire que ce n'est pas anodin et que cela peut générer des erreurs et des régressions dans votre application.
Exemple :
DECLARE @decimal1 decimal(8,4), @decimal2 decimal(8,4), @decimal3 decimal(8,4);
SET @decimal1 = 47;
SET @decimal2 = 0.002;
SET @decimal3 =  @decimal1 / @decimal2;
SELECT @decimal3 / @decimal1 * @decimal2 AS "Doit être égale à 1";
Résultat :
Msg 8115, Niveau 16, État 8, Ligne 4
Une erreur de dépassement arithmétique s'est produite lors de la conversion de numeric en type de données numeric.
alors qu'en utilisant le type Float le programme ne générait aucune erreur !

Le type Float peut être quelque fois déroutant pour le développeur !  Exemple :
DECLARE @f float
SELECT @f = 1111146804871.1111222233334444
SELECT @f
Résultat :
1111146804871.11 
On voit ainsi s'évaporer par magie plusieurs chiffres de précision ! Cette perte de précision est due au caractère approximatif de la représentation interne en mémoire du type Float comme cela a été expliqué ci-haut.

Le type Float comme le type Decimal représentent chacun un "domaine" au sens relationnel du terme; c.à.d. l’ensemble dans lequel les données d'un attribut (ie une colonne d'une relation) prennent valeur.
Le domaine Float, est défini par intention :
Float (n) ensemble des nombres en virgule flottante codé sur 32 bits (soit 4 octets) si n <= 24 ou sur 64 bit (8 octets) si 25<=n<=53
Comparé au domaine Decimal défini également par intention :
Decimal[ (p[ ,s] )] ensemble des numériques ayant une précision et une échelle fixes. Où p représente la précision et s représente l'échelle.
Précision  Taille de stockage (octets)
1–9     5 octets
10-19   9 octets
20-28  13 octets 
29-38  17 octets
Les 2 domaines Float et Decimal ne sont donc pas identiques et ne couvrent pas le même spectre de valeurs numériques.
Le type Float permet de représenter des valeurs allant de l'infiniment petit (jusqu'à la limite +/- 2.23E-308), à l'infiniment grand (jusqu'à la limite de +/- 1.79E+308).
Vous ne pourrez jamais représenter, par exemple, le nombre 1.79E+308 en utilisant le type Decimal ! Essayez :
SELECT CAST(1.79E+308 AS DECIMAL(38,0) ) -- résultat Une erreur de dépassement arithmétique
SELECT CAST(1.79E+308 AS DECIMAL(38,1) ) -- résultat Une erreur de dépassement arithmétique
SELECT CAST(1.79E+308 AS DECIMAL(38,2) ) -- résultat Une erreur de dépassement arithmétique
-- .... 
SELECT CAST(1.79E+308 AS DECIMAL(38,38) ) -- résultat Une erreur de dépassement arithmétique
De même (même raisonnement), vous ne pourrez jamais représenter l'infiniment petit, par exemple, le nombre 2.23E-308 en utilisant le type Decimal !
Donc, sachez que vous ne pouvez pas toujours remplacer le type Float par le type Decimal.

Autres remarques importantes

Beaucoup ont tendance à considérer le type Float comme un ensemble purement mathématique, le confondant souvent avec l'ensemble R (ensemble des nombres réels en mathématique), ce qui est une erreur.
L'ensemble R, au sens mathématique du terme, est un espace topologique au sens limite et continuité.
Or le type Float, par définition représenté physiquement, en mémoire, en binaire, par des 0 et 1, au plus sur 8 octets, ne pourra jamais représenter un espace topologique intégrant les notions de limite et de continuité. Il ne peut donc s'agir, au mieux, que d'un espace ou d'une topologie discrète où les points sont isolés les uns des autres.

Ci-dessous quelques résultats, qui sur le plan mathématique pur, peuvent apparaître surprenant voire même déroutant !
SELECT '(-2.23E-308) < 0' AS Expression,  
       CASE WHEN (-2.23E-308) < 0 THEN 'Vrai' ELSE 'Faux' END Resultat 
UNION ALL 
SELECT '(-1.115E-308) < 0' AS Expression,  
       CASE WHEN (-1.115E-308) < 0 THEN 'Vrai' ELSE 'Faux' END Resultat 
UNION ALL 
SELECT '(-1.115E-308) = 0' AS Expression,  
       CASE WHEN (-1.115E-308) = 0 THEN 'Vrai' ELSE 'Faux' END Resultat 
UNION  ALL 
SELECT '(1.115E-308) > 0' AS Expression,  
       CASE WHEN (1.115E-308) > 0 THEN 'Vrai' ELSE 'Faux' END Resultat 
UNION ALL 
SELECT '(1.115E-308) = 0' AS Expression,  
       CASE WHEN (1.115E-308) = 0 THEN 'Vrai' ELSE 'Faux' END Resultat 
UNION ALL 
SELECT '(+2.23E-308) > 0' AS Expression,  
       CASE WHEN (2.23E-308) > 0 THEN 'Vrai' ELSE 'Faux' END Resultat
Résultat :
Expression          Resultat
(-2.23E-308) < 0    Vrai -- OK 
(-1.115E-308) < 0   Faux -- surprenant sur le plan mathématique !
(-1.115E-308) = 0   Vrai -- surprenant sur le plan mathématique !
(1.115E-308) > 0    Faux -- surprenant sur le plan mathématique !
(1.115E-308) = 0    Vrai -- surprenant sur le plan mathématique !
(+2.23E-308) > 0    Vrai -- OK
De même :
SELECT '(2.23E-308)' AS Expression, 2.23E-308 AS Resultat 
UNION ALL 
SELECT '0 + (2.23E-308)' AS Expression, 0 + 2.23E-308 AS Resultat 
UNION ALL 
SELECT '1 + (2.23E-308)' AS Expression, 1 + 2.23E-308 AS Resultat 
UNION ALL
SELECT '2 + (2.23E-308)' AS Expression, 2 + 2.23E-308 AS Resultat 
UNION ALL
SELECT '3 + (2.23E-308)' AS Expression, 3 + 2.23E-308 AS Resultat
Résultat :
Expression      Resultat
(2.23E-308)     2,23E-308  -- OK 
0 + (2.23E-308) 2,23E-308  -- OK 
1 + (2.23E-308) 1          -- surprenant sur le  plan mathématique !
2 + (2.23E-308) 2          -- surprenant sur le  plan mathématique !
3 + (2.23E-308) 3          -- surprenant sur le  plan mathématique !

Conclusion

  • Le type Decimal est un bon choix, et est approprié pour représenter les montants dont on connaît l'ordre de grandeur et la précision souhaitée.  
  •  Les types de données Float (ou Real) présentant un caractère approximatif, ne doivent pas être utilisées lorsqu'un comportement numérique exact est requis, comme dans les applications financières, les opérations comprenant des arrondis ou les vérifications d'égalité. Il faut utiliser, en lieu et place, en fonction de la nature de l'information, les types de données comme Integer, Decimal, Money ou Smallmoney, etc.. (extrait de http://msdn.microsoft.com/fr-fr/library/ms187912.aspx)  
  • Le type Float doit être réservé au calcul scientifique. Le type Float est approprié lorsqu'on veut représenter un très large éventail de valeurs allant de l’infiniment petit à l’infiniment grand, c.à.d pour représenter les nombres réels au sens mathématique du terme. Il y a des situations où l'utilisation du type Float s'avère incontournable, notamment lorsqu'on ne connaît pas à l'avance l'ordre de grandeur. (exemple une table représentant les distances séparant des « entités » (nanoparticules, atomes, villes, étoiles, etc..). dans ce cas les distances peuvent varier de l’infiniment petit à l’infiniment grand. et dans ce contexte, l'utilisation du type Decimal serait impossible et le type Float serait le plus approprié.
  • Le type Float SQL Server s'approche de la norme IEEE 754 (mais ne respecte pas complètement la norme IEEE 754). En effet, ni la valeur NaN (Not-a-Number,), ni l'infini ne peuvent être représentés. Toute tentative de conversion d'une chaîne non numérique en type numérique et de division par zéro entraîne une erreur. Beaucoup de développeurs n'avaient pas d'autres choix, et ont fini par représenter les NaN et l'infini par des valeurs NULL. (Pour plus de détails, voir http://msdn.microsoft.com/fr-fr/library/ms172002.aspx
  • Il faut évitez d'utiliser les colonnes de type Float (ou Real) dans les clause WHERE, plus particulièrement les opérateurs = et <>. L'idéal est de limiter les colonnes Foat et Real aux signes de comparaison > ou <.

Aucun commentaire:

Enregistrer un commentaire

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.