web-dev-qa-db-fra.com

Comment porter un nombre de tableau décimal à tableau entier d'octets

Remarque: Il s’agit plus d’un problème de logique/mathématique que d’un problème spécifique de C #.

J'ai ma propre classe appelée Number - elle contient très simplement deux tableaux d'octets distincts appelés Whole et Decimal. Ces tableaux d'octets représentent chacun un nombre entier infiniment grand, mais, lorsqu'ils sont mis ensemble, ils créent un nombre entier avec une partie décimale.

Les octets sont stockés dans un format little-endian, représentant un nombre. Je crée une méthode appelée AddNumbers qui ajoutera deux de ces Numbers ensemble.

Cette méthode repose sur une autre méthode appelée PerformAdd, qui ajoute simplement deux tableaux ensemble. Il prend simplement un pointeur sur le tableau d'octets final, un pointeur sur un tableau à ajouter et un pointeur sur le second tableau à ajouter - ainsi que la longueur de chacun d'entre eux. Les deux tableaux sont simplement nommés "plus grand" et "plus petit". Voici le code pour cette méthode:

    private static unsafe void PerformAdd(byte* finalPointer, byte* largerPointer, byte* smallerPointer, int largerLength, int smallerLength)
    {
        int carry = 0;

        // Go through all the items that can be added, and work them out.
        for (int i = 0; i < smallerLength; i++)
        {
            var add = *largerPointer-- + *smallerPointer-- + carry;

            // Stick the result of this addition in the "final" array.
            *finalPointer-- = (byte)(add & 0xFF);

            // Now, set a carry from this.
            carry = add >> 8;
        }

        // Now, go through all the remaining items (which don't need to be added), and add them to the "final" - still working with the carry.
        for (int i = smallerLength; i < largerLength; i++)
        {
            var wcarry = *largerPointer-- + carry;

            // Stick the result of this addition in the "final" array.
            *finalPointer-- = (byte)(wcarry & 0xFF);

            // Now, set a carry from this.
            carry = wcarry >> 8;
        }

        // Now, if we have anything still left to carry, carry it into a new byte.
        if (carry > 0)
            *finalPointer-- = (byte)carry;
    }

Le problème ne réside pas dans cette méthode, mais dans la façon dont je l’utilise. C'est la méthode AddNumbers qui l'utilise. Cela fonctionne bien - il organise les deux tableaux d’octets séparés en "plus grand" (sens plus large ayant une longueur d'octets plus grande) et en "plus petit". Et puis il crée des pointeurs, il le fait à la fois pour Whole et Decimal séparément. Le problème est avec la partie décimale.

Disons que nous additionnons les nombres 1251 et 2185 ensemble. Dans cette situation, vous obtiendrez 3436 - donc cela fonctionne parfaitement!

Prenons un autre exemple: vous avez les chiffres 4.6 et ajoutez 1.2 - encore une fois, cela fonctionne bien et vous obtenez 5.8. Le problème vient avec l'exemple suivant.

Nous avons 15.673 et 1.783, vous vous attendriez à 17.456, mais en réalité, cela retourne: 16.1456, et la raison en est qu'il ne porte pas le "1".

Alors, voici mon problème: comment pourrais-je mettre en œuvre un moyen de savoir quand et comment le faire?Voici le code de ma méthode AddNumbers:

    public static unsafe Number AddNumbers(Number num1, Number num2)
    {
        // Store the final result.
        Number final = new Number(new byte[num1.Whole.Length + num2.Whole.Length], new byte[num1.Decimal.Length + num2.Decimal.Length]);

        // We're going to figure out which number (num1 or num2) has more bytes, and then we'll create pointers to smallest and largest.
        fixed (byte* num1FixedWholePointer = num1.Whole, num1FixedDecPointer = num1.Decimal, num2FixedWholePointer = num2.Whole, num2FixedDecPointer = num2.Decimal,
            finalFixedWholePointer = final.Whole, finalFixedDecimalPointer = final.Decimal)
        {
            // Create a pointer and figure out which whole number has the most bytes.
            var finalWholePointer = finalFixedWholePointer + (final.Whole.Length - 1);
            var num1WholeLarger = num1.Whole.Length > num2.Whole.Length ? true : false;

            // Store the larger/smaller whole number lengths.
            var largerLength = num1WholeLarger ? num1.Whole.Length : num2.Whole.Length;
            var smallerLength = num1WholeLarger ? num2.Whole.Length : num1.Whole.Length;

            // Create pointers to the whole numbers (the largest amount of bytes and smallest amount of bytes).
            var largerWholePointer = num1WholeLarger ? num1FixedWholePointer + (num1.Whole.Length - 1) : num2FixedWholePointer + (num2.Whole.Length - 1);
            var smallerWholePointer = num1WholeLarger ? num2FixedWholePointer + (num2.Whole.Length - 1) : num1FixedWholePointer + (num1.Whole.Length - 1);

            // Handle decimal numbers.
            if (num1.Decimal.Length > 0 || num2.Decimal.Length > 0)
            {
                // Create a pointer and figure out which decimal has the most bytes.
                var finalDecPointer = finalFixedDecimalPointer + (final.Decimal.Length - 1);
                var num1DecLarger = num1.Decimal.Length > num2.Decimal.Length ? true : false;

                // Store the larger/smaller whole number lengths.
                var largerDecLength = num1DecLarger ? num1.Decimal.Length : num2.Decimal.Length;
                var smallerDecLength = num1DecLarger ? num2.Whole.Length : num1.Decimal.Length;

                // Store pointers for decimals as well.
                var largerDecPointer = num1DecLarger ? num1FixedDecPointer + (num1.Decimal.Length - 1) : num2FixedDecPointer + (num2.Decimal.Length - 1);
                var smallerDecPointer = num1DecLarger ? num2FixedDecPointer + (num2.Decimal.Length - 1) : num1FixedDecPointer + (num1.Decimal.Length - 1);

                // Add the decimals first.
                PerformAdd(finalDecPointer, largerDecPointer, smallerDecPointer, largerDecLength, smallerDecLength);
            }

            // Add the whole number now.
            PerformAdd(finalWholePointer, largerWholePointer, smallerWholePointer, largerLength, smallerLength);
        }

        return final;
    }
7
ABPerson

Si vous avez simplement besoin de BigDecimal en C #, je vous suggère simplement de rechercher et d’utiliser une implémentation existante. Par exemple https://Gist.github.com/nberardi/2667136 (je ne suis pas l'auteur, mais cela semble bien).

Si vous devez l'implémenter pour quelque raison que ce soit (école, etc.), alors je me contenterais d'utiliser BigInteger.

Si vous devez l'implémenter avec des tableaux d'octets ... Vous pouvez toujours bénéficier de l'idée d'utiliser l'échelle. Vous devez évidemment prendre des chiffres supplémentaires après vos opérations, tels que "PerformAdd", puis les reporter sur le numéro principal.

Cependant, les problèmes ne s'arrêtent pas là. Lorsque vous commencerez à mettre en œuvre la multiplication, vous rencontrerez plus de problèmes et vous devrez inévitablement mélanger les parties décimale et entière.

8.73*0.11 -> 0.9603 0.12*0.026 -> 0.00312

Comme vous pouvez le voir, les parties entières et décimales se mélangent, puis la partie décimale devient une séquence plus longue.

cependant, si vous les représentez comme:

873|2 * 11|2 -> 873*11|4 -> 9603|4 -> 0.9603 12|2 & 26|3 -> 12*26|5 -> 312|5 -> 0.00312

ces problèmes disparaissent.

2
aiodintsov

Le format que vous avez sélectionné est fondamentalement difficile à utiliser et je ne connais personne qui utilise le même format pour cette tâche. Par exemple, la multiplication ou la division dans ce format doit être très difficile à mettre en œuvre.

En fait, je ne pense pas que vous stockiez suffisamment d'informations pour restaurer uniquement la valeur en premier lieu. En quoi les représentations stockées dans votre format diffèrent-elles entre 0.1 et 0.01? Je ne pense pas que vous puissiez distinguer ces deux valeurs. 

Le problème que vous rencontrez est un effet secondaire moindre du même problème: vous stockez des représentations binaires pour des valeurs décimales et vous attendez à pouvoir impliquer une taille unique (nombre de chiffres) de la représentation decimal. Vous ne pouvez pas le faire car, lorsque survient un dépassement décimal, il n’est pas garanti qu’il obtienne également un dépassement dans votre valeur stockée basée sur 256. En fait, le plus souvent, cela ne se produit pas simultanément.

Je ne pense pas que vous puissiez résoudre ce problème autrement qu'en enregistrant explicitement quelque chose d'équivalent au nombre de chiffres après la virgule décimale. Et si vous allez le faire quand même, pourquoi ne pas passer à un format beaucoup plus simple d'un simple BigInteger (oui, cela fait partie de la bibliothèque standard bien qu'il n'y ait rien comme BigDecimal) et un scale? C'est le format utilisé par de nombreuses bibliothèques similaires. Dans ce format, 123.45 est stocké sous forme de paire de 12345 et -2 (pour la position décimale), tandis que 1.2345 est stocké sous forme de paire de 12345 et -4. La multiplication dans ce format est presque une tâche triviale (étant donné que BigInteger implémente déjà la multiplication, il vous suffit donc de pouvoir tronquer les zéros à la fin). L'addition et la soustraction sont moins triviales, mais vous devez d'abord faire correspondre les échelles des deux nombres à l'aide de la multiplication par 10, puis utiliser l'addition standard sur BigInteger, puis normaliser en arrière (supprimer les zéros à la fin). La division est toujours difficile et vous devez décider des stratégies d'arrondi que vous souhaitez prendre en charge, car la division de deux nombres ne correspond pas forcément à un nombre d'une précision fixe.

3
SergGr