Pourquoi «dtoa.c» contient tellement de code?

Je serai le premier à admettre que ma connaissance générale de la programmation de bas niveau est un peu clairsemée. Je comprends bien des concepts fondamentaux, mais je ne les utilise pas régulièrement. Cela dit, j'ai été absolument étonné de la quantité de code nécessaire pour dtoa.c.

Au cours des deux derniers mois, j'ai travaillé sur une implémentation ECMAScript dans C # et j'ai ralenti le remplissage des trous dans mon moteur. Hier soir, j'ai commencé à travailler sur Number.prototype.toString, décrit dans la section 15.7.4.2 de la spécification ECMAScript (pdf) . Dans la section 9.8.1 , la NOTE 3 offre un lien vers dtoa.c mais je cherchais un défi, alors j'ai attendu pour le voir. Voici ce que j'ai proposé.

private IDynamic ToString(Engine engine, Args args) { var thisBinding = engine.Context.ThisBinding; if (!(thisBinding is NumberObject) && !(thisBinding is NumberPrimitive)) { throw RuntimeError.TypeError("The current 'this' must be a number or a number object."); } var num = thisBinding.ToNumberPrimitive(); if (double.IsNaN(num)) { return new StringPrimitive("NaN"); } else if (double.IsPositiveInfinity(num)) { return new StringPrimitive("Infinity"); } else if (double.IsNegativeInfinity(num)) { return new StringPrimitive("-Infinity"); } var radix = !args[0].IsUndefined ? args[0].ToNumberPrimitive().Value : 10D; if (radix < 2D || radix > 36D) { throw RuntimeError.RangeError("The parameter [radix] must be between 2 and 36."); } else if (radix == 10D) { return num.ToStringPrimitive(); } var sb = new StringBuilder(); var isNegative = false; if (num < 0D) { isNegative = true; num = -num; } var integralPart = Math.Truncate(num); var decimalPart = (double)((decimal)num.Value - (decimal)integralPart); var radixChars = RadixMap.GetArray((int)radix); if (integralPart == 0D) { sb.Append('0'); } else { var integralTemp = integralPart; while (integralTemp > 0) { sb.Append(radixChars[(int)(integralTemp % radix)]); integralTemp = Math.Truncate(integralTemp / radix); } } var count = sb.Length - 1; for (int i = 0; i < count; i++) { var k = count - i; var swap = sb[i]; sb[i] = sb[k]; sb[k] = swap; } if (isNegative) { sb.Insert(0, '-'); } if (decimalPart == 0D) { return new StringPrimitive(sb.ToString()); } var runningValue = 0D; var decimalIndex = 1D; var decimalTemp = decimalPart; sb.Append('.'); while (decimalIndex < 100 && decimalPart - runningValue > 1.0e-50) { var result = decimalTemp * radix; var integralResult = Math.Truncate(result); runningValue += integralResult / Math.Pow(radix, decimalIndex++); decimalTemp = result - integralResult; sb.Append(radixChars[(int)integralResult]); } return new StringPrimitive(sb.ToString()); } 

Est-ce que quelqu'un qui a plus d'expérience dans la programmation à bas niveau explique pourquoi dtoa.c a environ 40 fois plus de code? Je ne peux pas imaginer que C # soit beaucoup plus productif.

Dtoa.c contient deux fonctions principales: dtoa (), qui convertit un double en chaîne, et strtod (), qui convertit une chaîne en double. Il contient également beaucoup de fonctions de support, dont la plupart sont pour sa propre mise en œuvre d'arithmétique de précision arbitraire. La réclamation de dtoa.c à la renommée permet d'obtenir ces conversions, et cela ne peut se faire qu'en général avec une arithmétique de précision arbitraire. Il a également un code pour redondir les conversions correctement dans quatre modes d'arrondi différents.

Votre code essaie uniquement de mettre en œuvre l'équivalent de dtoa (), et comme il utilise un point flottant pour effectuer ses conversions, il ne les récupérera pas toujours. (Mise à jour: voir mon article http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/ pour plus de détails.)

(J'ai beaucoup écrit à ce sujet sur mon blog, http://www.exploringbinary.com/ . Six de mes sept derniers articles portaient sur les conversions strtod (). Lisez-les pour voir à quel point il est compliqué de faire Conversions correctement arrondies.)

Produire de bons résultats pour les conversions entre les représentations de virgule flottante décimale et binaire est un problème assez difficile.

La principale source de difficulté est que de nombreuses fractions décimales, même simples, ne peuvent pas être exprimées avec précision en utilisant un point flottant binaire – par exemple, 0.5 peut (évidemment), mais 0.1 ne peut pas. Et, dans l'autre sens (du binaire au décimal), vous ne voulez généralement pas le résultat absolument précis (par exemple, la valeur décimale précise du nombre le plus proche à 0.1 qui peut être représenté dans un double conforme à l'IEEE-754 est 0.1000000000000000055511151231257827021181583404541015625 ), donc vous voulez normalement arrondir.

Donc, la conversion implique souvent une approximation. Les bonnes routines de conversion garantissent la meilleure approximation possible dans des contraintes particulières (taille de mot ou nombre de chiffres). C'est d'où vient la majeure partie de la complexité.

Jetez un oeil sur le document cité dans le commentaire en haut de la dtoa.c œuvre de dtoa.c , Clinger's Comment lire les numéros de points flottants avec précision , pour une saveur du problème; Et peut-être le document de David M. Gay (l'auteur), les conversions binaire-décimale et décimale-binaire correctement calculées .

(En outre, plus généralement: ce que chaque informaticien devrait savoir sur l'arithmétique à point flottant .)

Sur la base d'un coup d'œil rapide, une bonne partie de la version C concerne de multiples plates-formes, et il semble que ce fichier soit génétiquement utilisable dans tous les compilateurs (C et C ++), bitnesses, implémentations en virgule flottante et plates-formes ; Avec des tonnes de configurations #define .

Je pense aussi que le code dans dtoa.c pourrait être plus efficace (indépendamment du langage). Par exemple, il semble faire un peu de violon, ce qui, dans les mains d'un expert, signifie souvent une vitesse. Je suppose qu'il utilise simplement un algorithme moins intuitif pour des raisons de vitesse.

Réponse courte: parce que dtoa.c fonctionne.

C'est exactement la différence entre un produit bien débité et un prototype NIH.