C#
a le decimal
type qui est utilisé pour les nombres qui nécessitent une représentation exacte en base 10. Par exemple, 0.1
ne peut pas être représenté en base 2 (par exemple float
et double
) et sera toujours une approximation lorsqu'il est stocké dans des variables de ces types.
Je me demandais si le fait inversé était également possible. Y a-t-il des nombres qui ne sont pas représentables en base 10 mais qui peuvent être représentés en base 2 (auquel cas je souhaiterais utiliser un float
au lieu d'un decimal
pour les gérer)?
Voici la clé de votre dilemme: 10
est le produit de 2
et 5
. Vous pouvez représenter n'importe quel nombre exactement en décimales de base 10 qui est k * 1/2n * 1/5m où k
, n
et m
sont des entiers.
Autrement dit - si le nombre n
dans 1/n contient un facteur qui ne fait pas partie des facteurs de la base, le nombre ne pourra pas être représenté exactement en un nombre fixe de chiffres dans le binaire/décimal/quelle que soit l'expansion de ce nombre - il aura une partie répétitive. Par exemple 1/15 = 0,0666666666 .... parce que 3 (15 = 3 * 5) n'est pas un facteur de 10.
Ainsi, tout ce qui peut être représenté exactement en base 2 (k * 1/2n) peut être représenté exactement en base 10.
Au-delà de cela, il y a le problème du nombre de chiffres/bits que vous utilisez pour représenter le nombre. Il y a des nombres qui peuvent être représentés exactement dans une base, mais cela prend plus qu'un certain nombre de chiffres/bits à faire.
En binaire, le nombre 1/10 qui est commodément 0,1 en décimal ne peut pas être représenté comme un nombre qui peut être représenté dans un nombre fixe de bits en binaire. Au lieu de cela, le nombre est 0,00011001100110011 ...2 (avec la partie 0011 se répétant pour toujours).
Regardons le numéro 12/ 10102 un peu plus près.
____ 0,00011 + --------- 1010 | 1,00000 0 - 1 0 0 ---- 1 00 ------- - + 0 | -- 1 000 | 0 | ------ | répéter 1 0000 | bloc 1010 | ------ | 1100 | 1010 | ---- | 100 ---- +
C'est exactement le même type de chose que vous obtenez lorsque vous essayez de faire la longue division pour 1/3.
1/10, lorsque factorisé est 1/(21 * 51). Pour la base 10 (ou tout multiple de 10), ce nombre se termine et est connu sous le nom de --- (nombre régulier . Une expansion décimale qui se répète est connue sous le nom de répétition décimale , et les nombres qui continuent indéfiniment sans se répéter sont des nombres irrationnels.
Le math derrière cela plonge dans petit théorème de Fermat ... et une fois que vous commencez à dire Fermat ou théorème, il devient un question Math.SE .
Y a-t-il des nombres qui ne sont pas représentables en base 10 mais peuvent être représentés en base 2?
La réponse est non'.
Donc, à ce stade, nous devons tous être clairs: chaque expansion binaire de longueur fixe d'un nombre rationnel peut être représentée comme une expansion décimale de longueur fixe.
Permet regarder de plus près la décimale en C # ce qui nous amène à virgule flottante décimale dans .NET et compte tenu de l'auteur, j'accepte que c'est ainsi que cela fonctionne.
Le type décimal a les mêmes composants que tout autre nombre à virgule flottante: une mantisse, un exposant et un signe. Comme d'habitude, le signe n'est qu'un seul bit, mais il y a 96 bits de mantisse et 5 bits d'exposant. Cependant, toutes les combinaisons d'exposants ne sont pas valides. Seules les valeurs 0-28 fonctionnent et elles sont toutes toutes négatives: la valeur numérique est
sign * mantissa / 10exponent
. Cela signifie que les valeurs maximales et minimales du type sont +/ 296-1), et le plus petit nombre non nul en termes de magnitude absolue est 10-28.
Je soulignerai tout de suite qu'en raison de cette implémentation, il y a des nombres dans le type double
qui ne peuvent pas être représentés dans decimal
- ceux qui sont hors de la plage. Double.Epsilon
est 4.94065645841247e-324
qui ne peut pas être représenté dans un decimal
, mais peut être dans un double
.
Cependant, dans la plage que le nombre décimal peut représenter, il a plus de bits de précision que les autres types natifs et peut les représenter sans erreur.
Il existe d'autres types qui flottent. Il y a un --- (BigInteger en C # qui peut représenter un entier arbitrairement grand. Il n'y a pas d'équivalent à Java BigDecimal (qui peut représenter des nombres avec des chiffres décimaux jusqu'à 232 chiffres longs - qui est une plage considérable) exactement . Cependant, si vous fouillez un pe vous pouvez trouver des implémentations roulées à la main.
Il existe des langages qui ont également un type de données rationnel qui vous permet de représenter exactement les rationnels (de sorte que 1/3 est en fait 1/3).
Spécifiquement pour C # et le choix de flottant ou rationnel, je vais m'en remettre à Jon Skeet de la pinte flottante décimale en .NET :
La plupart des applications métier devraient probablement utiliser décimal plutôt que float ou double. Ma règle d'or est que les valeurs artificielles telles que la monnaie sont généralement mieux représentées avec une virgule flottante décimale: le concept d'exactement 1,25 dollar est tout à fait raisonnable, par exemple. Pour les valeurs du monde naturel, telles que les longueurs et les poids, les types à virgule flottante binaire ont plus de sens. Même s'il existe un "1,25 mètre exactement" théorique, cela ne se produira jamais en réalité: vous ne pourrez certainement jamais mesurer des longueurs exactes, et il est peu probable qu'elles existent même au niveau atomique. Nous sommes habitués à ce qu'il y ait une certaine tolérance.
Une fois que vous sortez de la plage des valeurs acceptables, la réponse est oui. Cela dit, presque tout ce qui se trouve dans la plage aura une représentation. Référence décimale C # Bien que cela ne soit pas indiqué dans la spécification, les nombres irrationnels ne peuvent pas être représentés exactement (par exemple, e1, pi, racine carrée de 2, etc.).
Le mot clé décimal désigne un type de données 128 bits. Comparé aux types à virgule flottante, le type décimal a une plus grande précision et une plage plus petite, ce qui le rend approprié pour les calculs financiers et monétaires. La plage approximative et la précision du type décimal sont indiquées dans le tableau suivant.
Précision: 28-29 chiffres significatifs
1 Merci à MichaelT de me rappeler un autre numéro irrationnel.
Un type à virgule flottante en base deux serait capable de représenter avec précision de nombreuses valeurs qu'un type en base dix de même taille ne pourrait pas. Toute valeur qui serait exactement représentable par un type base-2 d'une certaine taille serait exactement représentable dans un type base-dix de taille suffisante. La taille requise pour un type purement base dix pour représenter toutes les valeurs d'un nombre à virgule flottante binaire dépendrait de la plage d'exposants du type binaire; des centaines de bits pour un float
, ou des milliers pour un double
.
Cela étant dit, le type Decimal
est suffisamment grand pour qu'il aurait été possible de le rendre utilisable comme type "universel" capable de contenir la valeur de toute autre primitive numérique et de fournir en plus d'autres fonctionnalités supplémentaires (si rien d'autre, utilisez un bit pour indiquer si la valeur stockée est le résultat de la conversion d'un double
, et si ce bit est défini, utilisez 64 bits pour conserver la valeur en question). Microsoft a toutefois choisi de ne pas le faire. Par conséquent, la conversion d'un double
en Decimal
échouera complètement pour les grandes valeurs, provoquera l'arrondi des petites valeurs au 1E-28 le plus proche. De plus, même dans la plage dynamique de decimal
, la méthode de conversion ne sera pas "aller-retour". Par exemple, évaluer 1,0/3,0 comme double donnera 0,3333333333333333148, mais la conversion en décimal produira 0,33333333333333333m et la conversion de nouveau en double donnerait 0,333333333333333329818.