Mysql
 sql >> Base de données >  >> RDS >> Mysql

Meilleure pratique pour stocker les poids dans une base de données SQL ?

Vous affirmez qu'il existe des inexactitudes inhérentes aux nombres à virgule flottante. Je pense que cela mérite d'être exploré un peu avant.

Lors du choix d'un système numérique pour représenter un nombre (que ce soit sur une feuille de papier, dans un circuit informatique ou ailleurs), il y a deux distincts problèmes à prendre en compte :

  1. sa base; et

  2. son format .

Choisissez une base, n'importe quelle base…

Limité par un espace fini, on ne peut pas représenter un membre arbitraire d'un ensemble infini . Par exemple :quelle que soit la quantité de papier que vous achetez ou la taille de votre écriture, il serait toujours possible de trouver un nombre entier qui ne rentre pas dans l'espace donné (vous pouvez simplement ajouter des chiffres supplémentaires jusqu'à ce que le papier soit épuisé). Donc, avec des entiers , nous restreignons généralement notre espace fini pour ne représenter que ceux qui se situent dans un intervalle particulier, par ex. si nous avons de la place pour le signe positif/négatif et trois chiffres, nous pourrions nous limiter à l'intervalle [-999,+999] .

Tous intervalle non vide contient un ensemble infini de nombres réels. En d'autres termes, peu importe l'intervalle sur lequel on reprend les nombres réels —que ce soit [-999,+999] , [0,1] , [0.000001,0.000002] ou quoi que ce soit d'autre - il y a toujours un ensemble infini de réels dans cet intervalle (il suffit de continuer à ajouter des chiffres fractionnaires (non nuls)) ! Par conséquent, les nombres réels arbitraires doivent toujours être "arrondi" à quelque chose qui peut être représenté dans un espace fini.

L'ensemble des nombres réels pouvant être représentés dans un espace fini dépend du système numérique utilisé. Dans notre (familier) positionnel base-10 système, un espace fini suffira pour la moitié (0.510 ) mais pas pour un tiers (0.33333…10 ); en revanche, dans la position (moins familière) base-9 système, c'est l'inverse (ces mêmes chiffres sont respectivement 0.44444…9 et 0.39 ). La conséquence de tout cela est que certains nombres qui peuvent être représentés en utilisant seulement une petite quantité d'espace en base 10 de position (et donc apparaissent être très "rond" pour nous les humains), par ex. un dixième, exigerait en fait que des circuits binaires infinis soient stockés avec précision (et n'apparaissent donc pas très "ronds" à nos amis numériques) ! Notamment, puisque 2 est un facteur de 10, il n'en va pas de même en sens inverse :tout nombre qui peut être représenté avec un binaire fini peut également être représenté avec un décimal fini.

Nous ne pouvons pas faire mieux pour des quantités continues. En fin de compte, ces quantités doivent utiliser une représentation finie dans quelques système numérique :il est arbitraire de savoir si ce système se trouve être facile sur les circuits informatiques, sur les doigts humains, sur autre chose ou sur rien du tout - quel que soit le système utilisé, la valeur doit être arrondi et donc toujours entraîne une "erreur de représentation".

En d'autres termes, même si l'on dispose d'un instrument de mesure parfaitement précis (ce qui est physiquement impossible), alors toute mesure qu'il rapporte aura déjà été arrondie à un nombre qui s'adapte à son affichage (quelle que soit la base qu'il utilise - généralement décimal, pour des raisons évidentes). Ainsi, "86,2 oz" n'est jamais réellement "86,2 oz " mais plutôt une représentation de "quelque chose entre 86,1500000... oz et 86,2499999... oz ". (En fait, parce qu'en réalité l'instrument est imparfait, tout ce que nous pouvons vraiment dire, c'est que nous en avons degré de confiance que la valeur réelle se situe dans cet intervalle, mais cela s'éloigne définitivement du point ici).

Mais nous pouvons faire mieux pour des quantités discrètes . De telles valeurs ne sont pas des "nombres réels arbitraires" et donc rien de ce qui précède ne s'applique à elles :elles peuvent être représentées exactement dans le système numérique dans lequel ils ont été définis - et en effet, devraient l'être (car la conversion vers un autre système numérique et la troncature à une longueur finie entraîneraient un arrondi à un nombre inexact). Les ordinateurs peuvent (inefficacement) gérer de telles situations en représentant le nombre sous forme de chaîne :par ex. considérez ASCII ou BCD encodage.

Appliquer un format…

Puisqu'il s'agit d'une propriété de la base (quelque peu arbitraire) du système numérique, qu'une valeur semble ou non "ronde" n'a aucune incidence sur sa précision . C'est une observation vraiment importante , ce qui va à l'encontre de l'intuition de nombreuses personnes (et c'est la raison pour laquelle j'ai passé tant de temps à expliquer la base numérique ci-dessus).

La précision est plutôt déterminée par combien de chiffres significatifs une représentation a . Nous avons besoin d'un format de stockage capable d'enregistrer nos valeurs à au moins autant de chiffres significatifs que nous considérons qu'ils sont corrects . Prenant à titre d'exemple des valeurs que nous considérons comme correctes lorsqu'elles sont indiquées comme 86.2 et 0.0000862 , les deux options les plus courantes sont :

  • Point fixe , où le nombre de chiffres significatifs dépend de l'ampleur :par exemple. dans une représentation fixe à 5 décimales, nos valeurs seraient stockées sous la forme 86.20000 et 0.00009 (et ont donc respectivement 7 et 1 chiffres significatifs de précision). Dans cet exemple, la précision a été perdue dans cette dernière valeur (et en effet, il n'en faudrait pas beaucoup plus pour que nous soyons totalement incapables de représenter rien d'importance); et l'ancienne valeur stockée fausse précision , ce qui est un gaspillage de notre espace fini (et en effet, il n'en faudrait pas beaucoup plus pour que la valeur devienne si grande qu'elle déborde de la capacité de stockage).

    Un exemple courant de cas où ce format pourrait être approprié est celui d'un système comptable :les sommes monétaires doivent généralement être suivies au centime près quelle que soit leur ampleur (par conséquent, moins de précision est requise pour les petites valeurs, et plus de précision est requise pour les grandes valeurs). Il se trouve que la monnaie est généralement également considérée comme discrète (les centimes sont indivisibles), c'est donc aussi un bon exemple d'une situation où une base particulière (décimale pour la plupart des monnaies modernes) est souhaitable pour éviter les erreurs de représentation discutées ci-dessus. /P>

  • Point flottant , où le nombre de chiffres significatifs est constant quelle que soit l'ampleur :par exemple. en représentation décimale à 5 chiffres significatifs, nos valeurs seraient stockées sous la forme 86.200 et 0.000086200 (et, par définition, avoir 5 chiffres significatifs de précision les deux fois). Dans cet exemple, les deux valeurs ont été stockées sans aucune perte de précision; et ils ont tous les deux le même montant de fausse précision, ce qui est moins coûteux (et nous pouvons donc utiliser notre espace fini pour représenter une plage de valeurs beaucoup plus large, à la fois grandes et petites).

    Un exemple courant où ce format peut être approprié est l'enregistrement de toutes les mesures du monde réel :la précision des instruments de mesure (qui souffrent tous à la fois de systématique et aléatoire erreurs) est assez constante quelle que soit l'échelle, donc, étant donné suffisamment de chiffres significatifs (généralement autour de 3 ou 4 chiffres), aucune précision n'est perdue même si un changement de base a entraîné un arrondi à un nombre différent .

    Mais quelle est la précision des formats de stockage en virgule flottante utilisé par nos ordinateurs ?

    La chose la plus importante à réaliser est que ces formats sont, respectivement, supérieurs à dix mille et plus un billion fois plus précis que de dire "86,2" - même si les conversions exactes du binaire en décimal incluent une fausse précision erronée (que nous devons ignorer :plus d'informations à ce sujet sous peu) !

Remarquez également que les deux et corrigés les formats à virgule flottante entraînent une perte de précision lorsqu'une valeur est connue avec plus de précision que ce que le format prend en charge. De telles erreurs d'arrondi peut se propager dans les opérations arithmétiques pour donner des résultats apparemment erronés (ce qui explique sans doute votre référence aux "inexactitudes inhérentes" des nombres à virgule flottante) :par exemple, 3 × 3000 en virgule fixe à 5 places donnerait 999.99000 plutôt que 1000.00000; et 7 − ⁄50 en virgule flottante à 5 chiffres significatifs donnerait 0.0028600 plutôt que 0.0028571 .

Le domaine de l'l'analyse numérique se consacre à la compréhension de ces effets, mais il est important de réaliser que tout système utilisable (même en effectuant des calculs dans votre tête) est vulnérable à de tels problèmes car aucune méthode de calcul dont l'arrêt est garanti ne peut jamais offrir une précision infinie :considérons, par exemple, comment calculer l'aire d'un cercle, il y aura nécessairement une perte de précision dans la valeur utilisée pour π, qui se propagera dans le résultat.

Conclusion

  1. Les mesures du monde réel doivent utiliser une virgule flottante binaire :c'est rapide, compact, extrêmement précis et pas pire qu'autre chose (y compris la version décimale dont vous êtes parti). Depuis les types de données à virgule flottante de MySQL sont IEEE754, c'est exactement ce qu'ils offrent.

  2. Les applications de devise doivent utiliser un point fixe en deniers :bien qu'il soit lent et gaspille de la mémoire, il garantit à la fois que les valeurs ne sont pas arrondies à des quantités inexactes et que des centimes ne sont pas perdus sur de grosses sommes d'argent. Depuis les types de données à virgule fixe de MySQL sont des chaînes encodées en BCD, c'est exactement ce qu'elles offrent.

Enfin, gardez à l'esprit que les langages de programmation représentent généralement des valeurs fractionnaires à l'aide de virgule flottante binaire types :donc si votre base de données stocke des valeurs dans un autre format, vous devez faire attention à la manière dont elles sont introduites dans votre application, sinon elles risquent d'être converties (avec tous les problèmes qui en découlent) au niveau de l'interface.

Quelle est la meilleure option dans ce cas ?

J'espère vous avoir convaincu que vos valeurs peuvent en toute sécurité (et devraient ) être stocké dans des types à virgule flottante sans trop se soucier des "inexactitudes" ? N'oubliez pas qu'ils sont plus précise que ne l'a jamais été votre fragile représentation décimale à 3 chiffres significatifs :il vous suffit d'ignorer la fausse précision (mais il faut toujours faites-le quand même, même si vous utilisez un format décimal à virgule fixe).

En ce qui concerne votre question :choisissez l'option 1 ou 2 plutôt que l'option 3, cela facilite les comparaisons (par exemple, pour trouver la masse maximale, on peut simplement utiliser MAX(mass) , alors que le faire efficacement sur deux colonnes nécessiterait une certaine imbrication).

Entre ces deux, peu importe celui que l'on choisit - les nombres à virgule flottante sont stockés avec un nombre constant de bits significatifs indépendamment de leur échelle .

De plus, alors que dans le cas général, il peut arriver que certaines valeurs soient arrondies à des nombres binaires plus proches de leur représentation décimale d'origine en utilisant l'option 1, tandis que simultanément d'autres sont arrondies à des nombres binaires plus proches de leur représentation décimale d'origine en utilisant l'option 2, comme nous verrons bientôt de telles erreurs de représentation ne se manifester que dans la fausse précision qu'il faut toujours ignorer.

Cependant, dans ceci cas, car il arrive qu'il y ait 16 onces pour 1 livre (et 16 est une puissance de 2), les différences relatives entre les valeurs décimales d'origine et les nombres binaires stockés en utilisant les deux approches sont identiques :

  1. 5.387510 (pas 5.3367187510 comme indiqué dans votre question) serait stocké dans un flottant binary32 sous la forme 101.0110001100110011001102 (qui est 5.3874998092651367187510 ) :c'est 0.0000036% de la valeur d'origine (mais, comme indiqué ci-dessus, la "valeur d'origine" était déjà une représentation assez moche de la quantité physique qu'elle représente).

    Sachant qu'un float binaire32 ne stocke que 7 chiffres décimaux de précision, notre compilateur sait avec certitude que tout à partir du 8ème chiffre est définitivement fausse précision et donc doit être ignoré dans chaque cas—ainsi, à condition que notre valeur d'entrée ne nécessite pas plus de précision que cela (et si c'était le cas, binary32 était évidemment le mauvais choix de format), cela garantit un retour à une valeur décimale aussi ronde que celle de départ :5.38750010 . Cependant, nous devrions vraiment appliquer la connaissance du domaine à ce stade (comme nous le devrions avec n'importe quel format de stockage) pour éliminer toute autre fausse précision qui pourrait exister, comme ces deux zéros de fin.

  2. 86.210 serait stocké dans un flottant binary32 sous la forme 1010110.001100110011001102 (qui est 86.199996948242187510 ) :c'est aussi 0.0000036% de la valeur d'origine. Comme précédemment, nous ignorons ensuite les fausses précisions pour revenir à notre entrée d'origine.

Remarquez comment les représentations binaires des nombres sont identiques, à l'exception du placement du point de base (qui sont séparés par quatre bits) :

101.0110 00110011001100110
101 0110.00110011001100110

C'est parce que 5,3875 × 2 =86,2.