web-dev-qa-db-fra.com

Comment gérer de très grands nombres en Java sans utiliser Java.math.BigInteger

Comment pourrais-je faire de l'arithmétique, + -/*%!, Avec des entiers arbitrairement grands sans utiliser Java.math.BigInteger?

Par exemple, la factorielle de 90 renvoie 0 en Java ... Je voudrais pouvoir résoudre ce problème.

58
frodosamoa

Je pense qu'un programmeur aurait dû implémenter sa propre bibliothèque bignum une fois, alors soyez le bienvenu.

(Bien sûr, plus tard, vous comprendrez que BigInteger est meilleur et l’utilisez, mais c’est une expérience d’apprentissage précieuse.)

(Vous pouvez suivre le code source de ce cours life sur github . En outre, je l'ai refait (un peu poli) en un série de blogs en 14 parties .)

Création d'une classe simple Big number en Java

Alors, de quoi avons-nous besoin?

Tout d'abord, une représentation du nombre,

basé sur les types de données que Java nous donne.

Comme vous pensez que la conversion décimale est la partie la plus compliquée, restons dans un mode décimal. Pour plus d'efficacité, nous ne stockons pas de vrais chiffres décimaux, mais travaillons en base 1 000 000 000 = 10^9 < 2^30. Cela correspond à un Java int (jusqu'à 2^31 ou 2^32), et au produit de deux de ces s'intègre parfaitement dans un Java long.

final static int BASE = 1000000000;
final static int BASE_DECIMAL_DIGITS = 9;

Puis le digits-array:

private int[] digits;

Stocke-t-on les chiffres dans le petit ou le grand endian, c’est-à-dire les plus grosses parties en premier ou en dernier? Cela n'a pas vraiment d'importance, nous avons donc choisi le big-endian, car c'est ainsi que les humains veulent le lire. (Pour l'instant, nous nous concentrons sur les valeurs non négatives - nous ajouterons ensuite un bit de signe pour les nombres négatifs.)

À des fins de test, nous ajoutons un constructeur qui permet d’initialiser à partir d’un tel int [].

/**
 * creates a DecimalBigInt based on an array of digits.
 * @param digits a list of digits, each between 0 (inclusive)
 *    and {@link BASE} (exclusive).
 * @throws IllegalArgumentException if any digit is out of range.
 */
public DecimalBigInt(int... digits) {
    for(int digit : digits) {
        if(digit < 0 ||  BASE <= digit) {
            throw new IllegalArgumentException("digit " + digit +
                                               " out of range!");
        }
    }
    this.digits = digits.clone();
}

En prime, ce constructeur est également utilisable pour un seul int (si inférieur à BASE), et même pour aucun int (que nous interprétons comme 0). Donc, nous pouvons maintenant faire ceci:

DecimalBigInt d = new DecimalBigInt(7, 5, 2, 12345);
System.out.println(d);

Cela nous donne de.fencing_game.paul.examples.DecimalBigInt@6af62373, pas si utile. Donc, nous ajoutons une méthode toString():

/**
 * A simple string view for debugging purposes.
 * (Will be replaced later with a real decimal conversion.)
 */
public String toString() {
    return "Big" + Arrays.toString(digits);
}

La sortie est maintenant Big[7, 5, 2, 12345], ce qui est plus utile pour les tests, n'est-ce pas?

Deuxièmement, la conversion du format décimal.

Nous avons de la chance ici: notre base (10 ^ 9) est une puissance de la base à partir de laquelle nous voulons convertir (10). Ainsi, nous avons toujours le même nombre (9) de chiffres décimaux représentant un chiffre "notre format". (Bien sûr, au début, il peut y avoir moins de chiffres.) Dans le code suivant, decimal est une chaîne de chiffres décimaux.

 int decLen = decimal.length();
 int bigLen = (decLen-1) / BASE_DECIMAL_DIGITS + 1;

Cette formule étrange est une manière Java int d'écrire bigLen = ceil(decLen/BASE_DECIMAL_DIGITS). (J'espère que c'est correct, nous le testerons plus tard.)

 int firstSome = decLen - (bigLen-1) * BASE_DECIMAL_DIGITS;

C'est la longueur du premier bloc de chiffres décimaux, elle devrait être comprise entre 1 et 9 (inclus).

Nous créons notre tableau:

 int[] digits = new int[bigLen];

En boucle entre les chiffres à créer:

 for(int i = 0; i < bigLen ; i++) {

Chacun de nos chiffres est représenté par un bloc de chiffres dans le numéro d'origine:

    String block =
        decimal.substring(Math.max(firstSome + (i-1)*BASE_DECIMAL_DIGITS, 0),
                          firstSome +   i  *BASE_DECIMAL_DIGITS);

(Le Math.max est nécessaire ici pour le premier bloc plus court.) Nous utilisons maintenant la fonction habituelle d'analyse Integer et plaçons le résultat dans le tableau:

    digits[i] = Integer.parseInt(block);
}

A partir du tableau maintenant créé, nous créons notre objet DecimalBigInt:

return new DecimalBigInt(digits);

Voyons si cela fonctionne:

DecimalBigInt d2 = DecimalBigInt.valueOf("12345678901234567890");
System.out.println(d2);

Sortie:

Big[12, 345678901, 234567890]

Cela semble correct :-) Nous devrions aussi le tester avec d'autres nombres (de longueurs différentes).

La prochaine partie sera la mise en forme décimale, cela devrait être encore plus facile.

Troisièmement, la conversion au format décimal.

Nous devons générer nos chiffres individuels sous forme de 9 chiffres décimaux. Pour cela, nous pouvons utiliser la classe Formatter, qui supporte les chaînes de format de type printf.

Une variante simple serait ceci:

public String toDecimalString() {
    Formatter f = new Formatter();
    for(int digit : digits) {
        f.format("%09d", digit);
    }
    return f.toString();
}

Ceci retourne 000000007000000005000000002000012345 et 000000012345678901234567890 pour nos deux nombres. Cela fonctionne pour un aller-retour (c'est-à-dire que le donner à la méthode valueOf donne un objet équivalent), mais les zéros au début ne sont pas vraiment jolis à regarder (et pourraient créer une confusion avec les nombres octaux). Nous devons donc séparer notre belle boucle pour chaque boucle et utiliser une chaîne de formatage différente pour les premiers chiffres et les suivants.

public String toDecimalString() {
    Formatter f = new Formatter();
    f.format("%d", digits[0]);
    for(int i = 1 ; i < digits.length; i++) {
        f.format("%09d", digits[i]);
    }
    return f.toString();
}

Une addition.

Commençons par addition, car c'est simple (et nous pouvons en utiliser des parties pour la multiplication plus tard).

/**
 * calculates the sum of this and that.
 */
public DecimalBigInt plus(DecimalBigInt that) {
    ...
}

Je veux des noms de méthode que vous puissiez lire comme si vous lisiez la formule, ainsi plus, minus, times au lieu de add, subtract, multiply.

Alors, comment fonctionne l'addition? Cela fonctionne de la même façon que nous l’avons appris à l’école pour les nombres décimaux supérieurs à 9: additionnez les chiffres correspondants, et si le résultat est supérieur à 10 (ou BASE dans notre cas), portez-en un chiffre suivant. Cela peut amener le nombre résultant à avoir un chiffre de plus que celui d'origine.

Commençons par le cas simple où les deux nombres ont le même nombre de chiffres. Ensuite, cela ressemble simplement à ceci:

int[] result = new int[this.digits.length];
int carry = 0;
for(int i = this.digits.length-1; i > 0; i--) {
    int digSum = carry + this.digits[i] + that.digits[i];
    result[i] = digSum % BASE;
    carry = digSum / BASE;
}
if(carry > 0) {
    int[] temp = new int[result.length + 1];
    System.arraycopy(result, 0, temp, 1, result.length);
    temp[0] = carry;
    result = temp;
}
return new DecimalBigInt(result);

(Nous allons de droite à gauche afin de pouvoir porter tout dépassement au chiffre suivant. Ce serait un peu plus joli si nous avions décidé d'utiliser le format Little Endian.)

Si les deux nombres n'ont pas le même nombre de chiffres, cela devient un peu plus compliqué.

Pour que ce soit aussi simple que possible, nous l'avons divisé en plusieurs méthodes:

Cette méthode ajoute un chiffre à un élément du tableau (qui peut déjà contenir une valeur autre que zéro) et stocke le résultat dans le tableau. En cas de dépassement, nous le reportons au chiffre suivant (qui a l'indice un de moins, pas un de plus) au moyen d'un appel récursif. De cette façon, nous nous assurons que nos chiffres restent toujours dans la plage valide.

/**
 * adds one digit from the addend to the corresponding digit
 * of the result.
 * If there is carry, it is recursively added to the next digit
 * of the result.
 */
private void addDigit(int[] result, int resultIndex,
                      int addendDigit)
{
    int sum = result[resultIndex] + addendDigit;
    result[resultIndex] = sum % BASE;
    int carry = sum / BASE;
    if(carry > 0) {
        addDigit(result, resultIndex - 1, carry);
    }
}

La prochaine fait la même chose pour tout un tableau de chiffres à ajouter:

/**
 * adds all the digits from the addend array to the result array.
 */
private void addDigits(int[] result, int resultIndex,
                       int... addend)
{
    addendIndex = addend.length - 1;
    while(addendIndex >= 0) {
        addDigit(result, resultIndex,
                 addend[addendIndex]);
        addendIndex--;
        resultIndex--;
    }
}

Nous pouvons maintenant implémenter notre méthode plus:

/**
 * calculates the sum of this and that.
 */
public DecimalBigInt plus(DecimalBigInt that) {
    int[] result = new int[Math.max(this.digits.length,
                                    that.digits.length)+ 1];

    addDigits(result, result.length-1, this.digits);
    addDigits(result, result.length-1, that.digits);

    // cut of leading zero, if any
    if(result[0] == 0) {
        result = Arrays.copyOfRange(result, 1, result.length);
    }
    return new DecimalBigInt(result);
}

Nous pourrions faire un peu mieux ici si nous examinions auparavant si un débordement est possible et si nous ne créons le tableau plus grand que nécessaire.

Ah, un test: d2.plus(d2) donne Big[24, 691357802, 469135780], ce qui semble bien.

Multiplication.

Souvenons-nous de la rentrée des classes, comment avons-nous multiplié les plus grands nombres sur papier?

123 * 123
----------
      369   <== 123 * 3
     246    <== 123 * 2
    123     <== 123 * 1
  --------
    15129

Donc, nous devons multiplier chaque chiffre [i] du premier nombre par chaque chiffre [j] du deuxième nombre et ajouter le produit au chiffre [i + j] du résultat (et faire attention à le porter). Bien sûr, ici les index sont comptés de droite, pas de gauche. (Maintenant, j'aimerais vraiment avoir utilisé des nombres little-endian.)

Comme le produit de deux de nos chiffres peut sortir de la plage de int, nous utilisons long pour la multiplication.

/**
 * multiplies two digits and adds the product to the result array
 * at the right digit-position.
 */
private void multiplyDigit(int[] result, int resultIndex,
                           int firstFactor, int secondFactor) {
    long prod = (long)firstFactor * (long)secondFactor;
    int prodDigit = (int)(prod % BASE);
    int carry = (int)(prod / BASE);
    addDigits(result, resultIndex, carry, prodDigit);
}

Nous pouvons maintenant voir pourquoi j'ai déclaré ma méthode addDigits pour prendre un paramètre resultIndex. (Et je viens de changer le dernier argument en un paramètre varargs, pour pouvoir mieux l'écrire ici.)

Donc, voici la méthode de multiplication croisée:

private void multiplyDigits(int[] result, int resultIndex,
                            int[] leftFactor, int[] rightFactor) {
    for(int i = 0; i < leftFactor.length; i++) {
        for(int j = 0; j < rightFactor.length; j++) {

            multiplyDigit(result, resultIndex - (i + j),
                          leftFactor[leftFactor.length-i-1],
                          rightFactor[rightFactor.length-j-1]);
        }
    }
}

J'espère avoir les bons calculs d'indices. Avec une représentation little-endian, cela aurait été multiplyDigit(result, resultIndex + i + j, leftFactor[i], rightFactor[j]) - assez clair, n'est-ce pas?

Notre méthode times n'a plus qu'à allouer le tableau de résultats, invoquer multiplyDigits et envelopper le résultat.

/**
 * returns the product {@code this × that}.
 */
public DecimalBigInt times(DecimalBigInt that) {
    int[] result = new int[this.digits.length + that.digits.length];
    multiplyDigits(result, result.length-1, 
                   this.digits, that.digits);

    // cut off leading zero, if any
    if(result[0] == 0) {
        result = Arrays.copyOfRange(result, 1, result.length);
    }
    return new DecimalBigInt(result);
}

Pour tester, d2.times(d2) donne Big[152, 415787532, 388367501, 905199875, 19052100], ce qui correspond à ce que mon calcul Emacs calcule ici.

Comparaison

Nous voulons pouvoir comparer deux de nos objets. Donc, nous implémentons Comparable<DecimalBigInt> et sa méthode compareTo.

public int compareTo(DecimalBigInt that) {

Comment savoir si l'un de nos nombres est plus grand qu'un autre? Premièrement, nous comparons la longueur des tableaux. Comme nous avons pris soin de ne pas induire de zéros non significatifs (le faisons-nous?), Le tableau le plus long devrait avoir le plus grand nombre.

    if(this.digits.length < that.digits.length) {
        return -1;
    }
    if (that.digits.length < this.digits.length) {
        return 1;
    }

Si les longueurs sont identiques, nous pouvons comparer élément par élément. Puisque nous utilisons le gros endian (c'est-à-dire , le gros bout vient en premier ), nous commençons par le début.

    for(int i = 0; i < this.digits.length; i++) {
        if(this.digits[i] < that.digits[i]) {
            return -1;
        }
        if(that.digits[i] < this.digits[i]) {
            return 1;
        }
    }

Si tout était identique, nos chiffres sont évidemment identiques et nous pouvons retourner 0.

    return 0;
}

equals + hashCode()

Chaque bonne classe immuable doit implémenter equals() et hashCode() de manière appropriée (et compatible).

Pour notre hashCode(), nous additionnons simplement les chiffres, en les multipliant par un petit nombre premier pour nous assurer que la commutation de chiffres ne donne pas le même code de hachage:

/**
 * calculates a hashCode for this object.
 */
public int hashCode() {
    int hash = 0;
    for(int digit : digits) {
        hash = hash * 13 + digit;
    }
    return hash;
}

Dans la méthode equals(), nous pouvons simplement déléguer à la méthode compareTo, au lieu de réimplémenter le même algorithme:

/**
 * compares this object with another object for equality.
 * A DecimalBigInt is equal to another object only if this other
 * object is also a DecimalBigInt and both represent the same
 * natural number.
 */
public boolean equals(Object o) {
    return o instanceof DecimalBigInt &&
        this.compareTo((DecimalBigInt)o) == 0;
}

Donc, assez pour aujourd'hui. La soustraction (et peut-être des nombres négatifs) et la division sont plus compliqués, donc je les omets pour le moment. Pour calculer la factorielle de 90, cela devrait suffire.

Calcul de grandes factorielles:

Voici la fonction factorielle:

/**
 * calculates the factorial of an int number.
 * This uses a simple iterative loop.
 */
public static DecimalBigInt factorial(int n) {
    DecimalBigInt fac = new DecimalBigInt(1);
    for(int i = 2; i <= n; i++) {
        fac = fac.times(new DecimalBigInt(i));
    }
    return fac;
}

Cela nous donne

fac(90) = 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000

Conversion à partir de représentations à base arbitraire

Invité par la prochaine question de frodosamoa, j’ai écrit: ma réponse sur la façon de convertir des systèmes de nombres arbitraires (positionnels) dans celui dans lequel nous pouvons (ou voulons) calculer . (Dans l'exemple ci-dessous, j'ai converti de trinaire en décimal, alors que la question portait sur le nombre décimal au format binaire.)

Ici, nous voulons convertir un système de nombres arbitraires (d'accord, avec une radixe comprise entre 2 et 36, nous pouvons donc utiliser Character.digit() pour convertir des chiffres uniques en ints) en notre système avec radix BASE ( = 1.000.000.000, mais ce n'est pas vraiment important ici).

Fondamentalement, nous utilisons schéma de Horner pour calculer la valeur du polynôme avec les chiffres sous forme de coefficients au point donné par la base.

sum[i=0..n] digit[i] * radix^i

peut être calculé avec cette boucle:

value = 0;
for  i = n .. 0
  value = value * radix + digit[i]
return value

Comme nos chaînes d'entrée sont big-endian, nous n'avons pas à compter à rebours, mais nous pouvons utiliser une simple boucle for améliorée. (Cela semble plus moche en Java, car nous n'avons pas de surcharge d'opérateur, et pas d'autoboxing de int à notre type DecimalBigInt.)

public static DecimalBigInt valueOf(String text, int radix) {
    DecimalBigInt bigRadix = new DecimalBigInt(radix);
    DecimalBigInt value = new DecimalBigInt(); // 0
    for(char digit : text.toCharArray()) {
       DecimalBigInt bigDigit =
           new DecimalBigInt(Character.digit(digit, radix));
       value = value.times(bigRadix).plus(bigDigit);
    }
    return value;
}

Dans ma mise en œuvre actuelle , j’ai ajouté une vérification des erreurs (et une exception générée) pour nous assurer que nous avions vraiment un numéro valide, et bien sûr un commentaire sur la documentation.


Convertir en un système de position arbitraire est plus compliqué, car il implique le reste et la division (par la base arbitraire), ce que nous n’avons pas encore implémenté - donc pas pour l'instant. Ce sera fait lorsque j'aurai une bonne idée de la division. (Nous n'avons besoin que d'une division par petits nombres (un chiffre) ici, ce qui peut être plus facile qu'une division générale.)

Division par petits nombres

À l'école, j'ai appris division longue . Voici un exemple pour un petit diviseur (un chiffre), dans la notation que nous utilisons ici en Allemagne (avec des annotations sur les calculs en arrière-plan, que nous n'écririons pas normalement), en système décimal:

 12345 : 6 = 02057     1 / 6 =  0
-0┊┊┊┊                 0 * 6 =  0
──┊┊┊┊
 12┊┊┊                12 / 6 =  2
-12┊┊┊                 2 * 6 = 12
 ──┊┊┊
  03┊┊                 3 / 6 =  0
 - 0┊┊                 0 * 6 =  0
  ──┊┊
   34┊                34 / 6 =  5
  -30┊                 5 * 6 = 30
   ──┊
    45                45 / 6 =  7
   -42                 7 * 6 = 42
    ──
     3     ==> quotient 2057, remainder 3.

Bien entendu, nous n’avons pas besoin de calculer ces produits (0, 12, 0, 30, 42) et de les soustraire si nous avons une opération de reste natif. Ensuite, cela ressemble à ceci (bien sûr, nous n’aurions pas besoin ici d’écrire les opérations):

 12345 : 6 = 02057     1 / 6 =  0,   1 % 6 = 1
 12┊┊┊                12 / 6 =  2,  12 % 6 = 0
  03┊┊                 3 / 6 =  0,   3 % 6 = 3
   34┊                34 / 6 =  5,  34 % 6 = 4
    45                45 / 6 =  7,  45 % 6 = 3
     3
           ==> quotient 2057, remainder 3.

Cela ressemble déjà assez à division courte , si nous l'écrivons dans un autre format.

Nous pouvons observer (et prouver) ce qui suit:

Si nous avons un nombre à deux chiffres x dont le premier chiffre est inférieur à notre diviseur d, alors x / d est un nombre à un chiffre et x % d est également un nombre à un chiffre, inférieur à d. Ceci, combiné à l'induction, montre que nous n'avons jamais besoin que de diviser (avec le reste) des nombres à deux chiffres par notre diviseur.

Revenons à nos grands nombres avec radix BASE: tous les nombres à deux chiffres sont représentables sous la forme d'un Java long, et nous avons ici / et % en natif.

/**
 * does one step in the short division algorithm, i.e. divides
 *  a two-digit number by a one-digit one.
 *
 * @param result the array to put the quotient digit in.
 * @param resultIndex the index in the result array where
 *             the quotient digit should be put.
 * @param divident the last digit of the divident.
 * @param lastRemainder the first digit of the divident (being the
 *           remainder of the operation one digit to the left).
 *           This must be < divisor.
 * @param divisor the divisor.
 * @returns the remainder of the division operation.
 */
private int divideDigit(int[] result, int resultIndex,
                        int divident, int lastRemainder,
                        int divisor) {
    assert divisor < BASE;
    assert lastRemainder < divisor;

    long ent = divident + (long)BASE * lastRemainder;

    long quot = ent / divisor;
    long rem = ent % divisor;

    assert quot < BASE;
    assert rem < divisor;

    result[resultIndex] = (int)quot;
    return (int)rem;
}

Nous allons maintenant appeler cette méthode en boucle, en renvoyant toujours le résultat du précédent appel sous la forme lastRemainder.

/**
 * The short division algorithm, like described in
 * <a href="http://en.wikipedia.org/wiki/Short_division">Wikipedia's
 *   article <em>Short division</em></a>.
 * @param result an array where we should put the quotient digits in.
 * @param resultIndex the index in the array where the highest order digit
 *     should be put, the next digits will follow.
 * @param divident the array with the divident's digits. (These will only
 *          be read, not written to.)
 * @param dividentIndex the index in the divident array where we should
 *         start dividing. We will continue until the end of the array.
 * @param divisor the divisor. This must be a number smaller than
 *        {@link #BASE}.
 * @return the remainder, which will be a number smaller than
 *     {@code divisor}.
 */
private int divideDigits(int[] result, int resultIndex,
                         int[] divident, int dividentIndex,
                         int divisor) {
    int remainder = 0;
    for(; dividentIndex < divident.length; dividentIndex++, resultIndex++) {
        remainder = divideDigit(result, resultIndex,
                                divident[dividentIndex],
                                remainder, divisor);
    }
    return remainder;
}

Cette méthode retourne toujours un int, le reste.

Maintenant, nous voulons qu'une méthode publique retourne un DecimalBigInt, nous en créons donc une. Il a pour tâche de vérifier les arguments, de créer un tableau pour la méthode de travail, d'éliminer le reste et de créer un DecimalBigInt à partir du résultat. (Le constructeur supprime un zéro qui peut être présent.)

/**
 * Divides this number by a small number.
 * @param divisor an integer with {@code 0 < divisor < BASE}.
 * @return the integer part of the quotient, ignoring the remainder.
 * @throws IllegalArgumentException if the divisor is <= 0 or >= BASE.
 */
public DecimalBigInt divideBy(int divisor)
{
    if(divisor <= 0 || BASE <= divisor) {
        throw new IllegalArgumentException("divisor " + divisor +
                                           " out of range!");
    }

    int[] result = new int[digits.length];
    divideDigits(result, 0,
                 digits, 0,
                 divisor);
    return new DecimalBigInt(result);
}

Nous avons également une méthode similaire, qui renvoie le reste à la place:

/**
 * Divides this number by a small number, returning the remainder.
 * @param divisor an integer with {@code 0 < divisor < BASE}.
 * @return the remainder from the division {@code this / divisor}.
 * @throws IllegalArgumentException if the divisor is <= 0 or >= BASE.
 */
public int modulo(int divisor) {
    if(divisor <= 0 || BASE <= divisor) {
        throw new IllegalArgumentException("divisor " + divisor +
                                           " out of range!");
    }
    int[] result = new int[digits.length];
    return divideDigits(result, 0,
                        digits, 0,
                        divisor);
}

Ces méthodes peuvent être appelées comme ceci:

    DecimalBigInt d3_by_100 = d3.divideBy(100);
    System.out.println("d3/100 = " + d3_by_100);
    System.out.println("d3%100 = " + d3.modulo(100));

Conversion en radix arbitraire

Nous avons maintenant les bases pour convertir en radix arbitraire. Bien sûr, pas vraiment arbitraire, seules les radix inférieures à BASE sont autorisées, mais cela ne devrait pas être un problème trop important.

Comme déjà répondu dans une autre réponse à propos de la conversion des nombres, nous devons faire "division, reste, multiplier, ajouter. La partie" multiplier-ajouter "ne fait en fait que rassembler les chiffres individuels, de sorte que nous pouvons le remplacer par un simple tableau- accès.

Comme nous avons toujours besoin du quotient et du reste, nous n'utiliserons pas les méthodes publiques modulo et divideBy, mais appellerons à plusieurs reprises la méthode divideDigits.

/**
 * converts this number to an arbitrary radix.
 * @param radix the target radix, {@code 1 < radix < BASE}.
 * @return the digits of this number in the base-radix system,
 *     in big-endian order.
 */
public int[] convertTo(int radix)
{
    if(radix <= 1 || BASE <= radix) {
        throw new IllegalArgumentException("radix " + radix +
                                           " out of range!");
    }

Tout d'abord, un traitement des cas spéciaux pour 0.

    // zero has no digits.
    if(digits.length == 0)
        return new int[0];

Ensuite, nous créons un tableau pour les chiffres du résultat (assez long), et quelques autres variables.

    // raw estimation how many output digits we will need.
    // This is just enough in cases like BASE-1, and up to
    // 30 digits (for base 2) too much for something like (1,0,0).
    int len = (int) (Math.log(BASE) / Math.log(radix) * digits.length)+1;
    int[] rDigits = new int[len];
    int rIndex = len-1;
    int[] current = digits;
    int quotLen = digits.length;

quotLen est le nombre de chiffres (à l'exception des zéros non significatifs) du dernier quotient. Si c'est 0, nous avons fini.

    while(quotLen > 0)  {

Un nouveau tableau pour le prochain quotient.

        int[] quot = new int[quotLen];

L'opération quotient-et-reste. Le quotient est maintenant dans quot, le reste dans rem.

        int rem = divideDigits(quot, 0,
                               current, current.length - quotLen,
                               radix);

Nous plaçons le reste dans le tableau de sortie (en le remplissant à partir du dernier chiffre).

        rDigits[rIndex] = rem;
        rIndex --;

Ensuite, nous échangeons les tableaux pour le prochain tour.

        current = quot;

S'il y a des zéros en tête dans le quotient (il y en aura au plus un, puisque la base est plus petite que BASE), nous réduisons la taille du quotient de un. Le prochain tableau sera plus petit.

        if(current[0] == 0) {
            // omit leading zeros in next round.
            quotLen--;
        }
    }

Après la boucle, il peut y avoir des zéros en tête dans le tableau rDigits, et nous les avons supprimés.

    // cut of leading zeros in rDigits:
    while(rIndex < 0 || rDigits[rIndex] == 0) {
        rIndex++;
    }
    return Arrays.copyOfRange(rDigits, rIndex, rDigits.length);
}

C'est tout. Cela semble un peu compliqué, cependant. Voici un exemple d'utilisation:

    System.out.println("d4 in base 11: " +
                       Arrays.toString(d4.convertTo(11)));
    System.out.println("d5 in base 7: " +
                       Arrays.toString(d5.convertTo(7)));

Celles-ci affichent [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0] et [1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0], exactement les mêmes chiffres que ceux analysés précédemment (à partir d'une chaîne, cependant).

Sur cette base, nous pouvons également formater sous forme de chaîne:

/**
 * Converts the number to a String in a given radix.
 * This uses {@link Character.digit} to convert each digit
 * to one character.
 * @param radix the radix to use, between {@link Character.MIN_RADIX}
 *   and {@link Character.MAX_RADIX}.
 * @return a String containing the digits of this number in the
 *   specified radix, using '0' .. '9' and 'a' .. 'z' (as much as needed).
 */
public String toString(int radix) {
    if(radix < Character.MIN_RADIX || Character.MAX_RADIX < radix) {
        throw new IllegalArgumentException("radix out of range: " + radix);
    }
    if(digits.length == 0)
        return "0";
    int[] rdigits = convertTo(radix);
    StringBuilder b = new StringBuilder(rdigits.length);
    for(int Dig : rdigits) {
        b.append(Character.forDigit(Dig, radix));
    }
    return b.toString();
}
246
Paŭlo Ebermann

Vous voudrez peut-être implémenter ou rechercher une bibliothèque pour décimal codé binaire si vous essayez d'éviter BigInteger. Vous pouvez obtenir une factorielle de 90 avec BigInteger si vous voulez l'utiliser:

public static BigInteger factorial(BigInteger value) {
    BigInteger total = BigInteger.ONE;
    for (int i = 0; value.compareTo(BigInteger.ONE) == 1; i++) {
        total = total.multiply(value);
        value = value.subtract(BigInteger.ONE);
    }
    return total;
}
2
WhiteFang34

Utilisez le code ci-dessous pour multiplier les nombres de toutes longueurs: -

public class BigNumberMultiplication {


private static int[] firstBigNumber = null;
private static int[] secondBigNumber = null;

public static int[] baseMul(int[] baseMultiple, int base) {

    System.out.println("baseMultiple" + Arrays.toString(baseMultiple) + base);
    for (int i = 0; i < baseMultiple.length; i++) {
        baseMultiple[i] *= base;
    }
    System.out.println("basemultipleresultwithoutcarryforward" + baseMultiple);
    return carryForward(baseMultiple);
}

public static int[] basePowerMul(int[] basePowerMultiple, int base, int power) {

    int basePowerMultipleTemp[] = baseMul(basePowerMultiple, base);
    System.out.println("basePowerMultipleTemp" + Arrays.toString(basePowerMultipleTemp) + "power" + power);
    int basePowerMultipleResult[] = new int[basePowerMultipleTemp.length + (power - 1)];
    for(int i = 0; i < basePowerMultipleTemp.length; i++)
        basePowerMultipleResult[i] = basePowerMultipleTemp[i];
    if(power > 1){
    for(int i = 0; i < (power - 1); i++)
        basePowerMultipleResult[basePowerMultipleTemp.length + i] = 0;
    }
    System.out.println("basepowermulresult" + Arrays.toString(basePowerMultipleResult));
    return basePowerMultipleResult;
}
public static int[] addBigNumber(int[] finalNumberInArray, int[] finalNumberInArrayTemp){
    System.out.println("final number in array" + Arrays.toString(finalNumberInArray) + "finalNumberInTemp" + Arrays.toString(finalNumberInArrayTemp));
    int n = finalNumberInArray.length;
    for(int i = (finalNumberInArrayTemp.length - 1); i >= 0; i--){
        finalNumberInArray[n - 1] += finalNumberInArrayTemp[i];
        n--;
    }

    return carryForward(finalNumberInArray);

}

public static int[] carryForward(int[] arrayWithoutCarryForward){

    int[] arrayWithCarryForward = null;
    System.out.println("array without carry forward" + Arrays.toString(arrayWithoutCarryForward));
    for (int i = arrayWithoutCarryForward.length - 1; i > 0; i--) {
        if (arrayWithoutCarryForward[i] >= 10) {
            int firstDigit = arrayWithoutCarryForward[i] % 10;
            int secondDigit = arrayWithoutCarryForward[i] / 10;
            arrayWithoutCarryForward[i] = firstDigit;
            arrayWithoutCarryForward[i - 1] += secondDigit;
        } 
    }

    if(arrayWithoutCarryForward[0] >= 10){
        arrayWithCarryForward = new int[arrayWithoutCarryForward.length + 1];
        arrayWithCarryForward[0] = arrayWithoutCarryForward[0] / 10;
        arrayWithCarryForward[1] = arrayWithoutCarryForward[0] % 10;
    for(int i = 1; i < arrayWithoutCarryForward.length; i++)
        arrayWithCarryForward[i + 1] = arrayWithoutCarryForward[i];
    }
    else{
        arrayWithCarryForward = arrayWithoutCarryForward;
    }
    System.out.println("array with carry forward" + Arrays.toString(arrayWithCarryForward));
    return arrayWithCarryForward;
}
public static int[] twoMuscularNumberMul(){
    int finalNumberInArray[] = null;
    for(int i = 0; i < secondBigNumber.length; i++){
        if(secondBigNumber[i] == 0){}
        else {

             int[] finalNumberInArrayTemp = basePowerMul(Arrays.copyOf(firstBigNumber, firstBigNumber.length), secondBigNumber[i], secondBigNumber.length - i);
             if(finalNumberInArray == null){
                 finalNumberInArray = finalNumberInArrayTemp;
                 System.out.println("finalNumberInArray" + Arrays.toString(finalNumberInArray));
             }
             else{
                 finalNumberInArray = addBigNumber(finalNumberInArray, finalNumberInArrayTemp);
             System.out.println("finalNumberInArray" + Arrays.toString(finalNumberInArray));
             }
        }
    }
    return finalNumberInArray;
}

public static int [] readNumsFromCommandLine() {

    Scanner s = new Scanner(System.in);
    System.out.println("Please enter the number of digit");
    int count = s.nextInt();
    System.out.println("please enter the nuumber separated by space");
    s.nextLine();

    int [] numbers = new int[count];
    Scanner numScanner = new Scanner(s.nextLine());
    for (int i = 0; i < count; i++) {
        if (numScanner.hasNextInt()) {
            numbers[i] = numScanner.nextInt();
        } else {
            System.out.println("You didn't provide enough numbers");
            break;
        }
    }

    return numbers;
}
public static void main(String[] args) {

    firstBigNumber = readNumsFromCommandLine();
    secondBigNumber = readNumsFromCommandLine();
    System.out.println("1st number" + Arrays.toString(firstBigNumber) + "2nd number" + Arrays.toString(secondBigNumber));
    int[] finalArray = twoMuscularNumberMul();
    System.out.println(Arrays.toString(finalArray));

    }

}
1
user2130532

Les opérations arithmétiques en Java utilisant les opérateurs +, -, *, / et % sont liées par les contraintes des types de données primitifs Java .

Cela signifie que si vous ne pouvez pas insérer les nombres souhaités dans la plage de, disons double ou long, vous devrez alors utiliser une bibliothèque "grand nombre", telle que celle intégrée à Java ( BigDecimal , BigInteger ), ou une bibliothèque tierce, ou écrivez la vôtre. Cela signifie également que vous ne pouvez pas utiliser les opérateurs arithmétiques, car Java ne prend pas en charge la surcharge des opérateurs.

1
maerics

Si nous voulons effectuer des opérations arithmétiques sur de très grands nombres, ils doivent être sous forme d'objet, tel que Strings.

Laissez-les être des chaînes avec une longueur de caractère supérieure à la plage de BigInteger. 

Dans ce cas, je vais effectuer une opération arithmétique comme nous le faisons sur un cahier. Par exemple - Supposons que nous devions faire l'addition. Commencez par comparer les deux chaînes pour la longueur . Créez trois nouvelles chaînes . La première chaîne est la plus petite . La deuxième chaîne est la sous-chaîne la plus à droite de la chaîne la plus longue, avec une longueur égale à la longueur. chaîne plus petite . La troisième chaîne est la chaîne longue restante du côté gauche . Ajoutez maintenant les première et deuxième chaînes de la fin convertissant des caractères en entiers, un caractère à la fois et en conservant le report dans une variable int. . Immédiatement après chaque addition, ajoutez la somme dans un StringBuffer. Une fois les deux chaînes ajoutées, effectuez la même opération pour la troisième chaîne et continuez d’ajouter le report. Finalement, inversez le StringBuffer et renvoyez la chaîne.

Voici le code que j'ai utilisé pour l'addition

public String addNumber(String input1,String input2){
int n=0;String tempStr;
String one="";
String two="";
if(input1.length()>input2.length()){
    n=input1.length()-input2.length();
    tempStr=new String(input1);
    one=new String(input1.substring(n,input1.length()));
    two=new String(input2);
}else{
    n=input2.length()-input1.length();
    tempStr=new String(input2);
    one=new String(input2.substring(n,input2.length()));
    two=new String(input1);
}
StringBuffer temp=new StringBuffer();
for(int i=0;i<n;i++){
    temp.append(tempStr.charAt(i));
}
StringBuffer newBuf=new StringBuffer();
int carry=0;
int c;
for(int i=one.length()-1;i>=0;i--){
    int a=Character.getNumericValue(one.charAt(i));
    int b=Character.getNumericValue(two.charAt(i));
    c=a+b+carry;

    newBuf.append(""+(c%10));
    c=c/10;
    carry=c%10;
}
String news=new String(temp);
for(int i=news.length()-1;i>=0;i--){
c=(Character.getNumericValue(news.charAt(i)))+carry;
newBuf.append(""+(c%10));
c=c/10;
carry=c%10;
}
if(carry==1){
    newBuf.append(""+carry);
}
String newisis=new String(newBuf.reverse());
return newisis;
}
0
Shubhdeep Singh

texte fort public class BigInteger {

     public static String checkSignWithRelational(int bigInt1, int bigInt2){
            if( bigInt1 < 0){
                return "negative";
            }else {
                return "positive";
            }
     }
     BigInteger( long init)
     {
         Long.parseLong(bigInt1);
     }
     BigInteger String (String init){
        return null; 
     }

    private static int intLenght(int bigInt) {

        return Integer.toString(bigInt).length();
    }

    private static int[] intToArray(int bigInt, int bigIntLength, int arrayLength) {

        int array[] = new int[arrayLength ]; 
        for (int i = 0; i < arrayLength ; i++) {
            array[i] = ( i<bigIntLength ?
                             getDigitAtIndex(bigInt, bigIntLength - i -1) :0 ); 
        }
        return array;
}
    static String add(int bigInt1, int bigInt2) {
        //Find array length
        int length1 = intLenght(bigInt1);
        int length2 = intLenght(bigInt2);
        int arrayLength = Math.max(length1, length2);


        int array1[] = intToArray(bigInt1, length1, arrayLength);
        int array2[] = intToArray(bigInt2, length2, arrayLength);


        return add(array1, array2);
    }


    private static String add(int[] array1, int[] array2) {
        int carry=0;
        int addArray[] = new int[array1.length + 1];


        for (int i = 0; i < array1.length; i++) {
            addArray[i] = (array1[i] + array2[i] + carry) % 10 ; 
            carry = (array1[i] + array2[i] + carry) / 10; 
        }
        addArray[array1.length] = carry;
        return arrayToString(addArray);
    }

    private static int getDigitAtIndex(int longint,int index){        
        return Integer.parseInt(Integer.toString(longint).substring(index, index+1)); 
    }
    private static String arrayToString(int[] addArray) {
        String add = "";
        boolean firstNonZero = false; 
        for (int i = addArray.length-1; i >= 0 ; i--) {  

            if(!firstNonZero && (addArray[i]==0)){ 
                continue;
            } else{
                firstNonZero=true;
            }
            add += addArray[i];
            if((i%3 ==0)&&i!=0){ add +=",";}  //formatting
        }
        String sumStr = add.length()==0?"0":add; 
        return sumStr;
    }
    public static String sub(int bigInt1, int bigInt2) {


        int length1 = intLenght(bigInt1);
        int length2 = intLenght(bigInt2);
        int arrayLength = Math.max(length1, length2);


        int array1[] = intToArray(bigInt1, length1, arrayLength);
        int array2[] = intToArray(bigInt2, length2, arrayLength);


        return sub(array1, array2);
    }
    private static String sub(int[] array1, int[] array2) {
        int carry=0;
        int sub[] = new int[array1.length + 1];


        for (int i = 0; i < array1.length; i++) {
            sub[i] = (array1[i] - array2[i] + carry) % 10 ; //sum digits + carry; then extract last digit
            carry = (array1[i] - array2[i] + carry) / 10; //Compute carry
        }
        sub[array1.length] = carry;
        return arrayToString(sub);
    }
    public static String mul(int bigInt1, int bigInt2) {
        int length1 = intLenght(bigInt1), length2 = intLenght(bigInt2), length = Math.max(length1, length2);        
        int array1[] = intToArray(bigInt1, length1, length); int array2[] = intToArray(bigInt2, length2, length);
        return mul(array1, array2);
    }
    private static String mul(int[] array1, int[] array2) {
        int product[] = new int[array1.length + array2.length];
        for(int i=0; i<array1.length; i++){        
            for(int j=0; j<array2.length; j++){ 

                int prod = array1[i] * array2[j];       
                int prodLength = intLenght(prod);
                int prodAsArray[] =  intToArray(prod, prodLength, prodLength); 


                for (int k =0; k < prodAsArray.length; k++) {
                    product[i+j+k] += prodAsArray[k];


                    int currentValue = product[i+j+k];
                    if(currentValue>9){
                        product[i+j+k] = 0;                
                        int curValueLength = intLenght(currentValue);
                        int curValueAsArray[] = intToArray(currentValue, curValueLength, curValueLength);
                        for (int l = 0; l < curValueAsArray.length; l++) {
                            product[i+j+k+l] += curValueAsArray[l];
                        }
                    }
                }      
            }
        }
        return arrayToString(product);
    }

   public static int div(int bigInt1, int bigInt2) {
       if ( bigInt2 == 0){
           throw new ArithmeticException("Division by 0 is undefined:" + bigInt1+ "/" + bigInt2);
       }
       int sign = 1;
       if(bigInt1 < 0) {
           bigInt1 = -bigInt1;
           sign = -sign;
       }
       if (bigInt2 < 0){
           bigInt2 = -bigInt2;
           sign = -sign;

       }
       int result  =0;
       while (bigInt1 >= 0){
           bigInt1 -= bigInt2;
           result++;
       }
       return (result - 1) * sign;
   }

    public static String check(String bigInt1, String bigInt2){
        int difference;
        StringBuilder first = new StringBuilder(bigInt1);
        StringBuilder second = new StringBuilder(bigInt2);

        if(bigInt1.length()> bigInt2.length()){
            difference = bigInt1.length() - bigInt2.length();
            for(int x = difference; x > 0; x--){
                second.insert(0,"0");

            }
        bigInt2 = second.toString();
        return bigInt2;

        }else {
            difference = bigInt2.length() - bigInt1.length();
            for (int x = difference; x> 0; x--)
            {
                first.insert(0, "0");
            }
            bigInt1 = first.toString();
            return bigInt1;
        }
    }
    public static int mod(int bigInt1, int bigInt2){
        int res = bigInt1 % bigInt2;
        return (res);

    }

    public static void main(String[] args) {

        int bigInt1 = Integer.parseInt("987888787");
        int bigInt2 = Integer.parseInt("444234343");
        System.out.println(bigInt1+" + "+bigInt2+" = "+add(bigInt1, bigInt2));
        System.out.println(bigInt1+" - "+bigInt2+" = "+sub(bigInt1, bigInt2));
        System.out.println(bigInt1+" * "+bigInt2+" = "+mul(bigInt1, bigInt2));
        System.out.println(bigInt1+" / "+bigInt2+" = "+div(bigInt1, bigInt2));
        System.out.println(bigInt1+" % "+bigInt2+" = "+mod(bigInt1, bigInt2));
    }

}

0
BBsal

Quand je veux faire 90! ou un autre calcul massif, j'essaie d'utiliser un tableau int [], chaque élément contenant l'un des chiffres. Ensuite, j'applique la multiplication traditionnelle en utilisant un crayon et du papier pour obtenir la réponse dans un autre tableau int [].

C'est le code que j'ai écrit en Java et qui en calcule 100! plûtot vite. N'hésitez pas à l'utiliser comme bon vous semble.

public int factoial(int num) {
    int sum = 0;
    int[][] Dig = new int[3][160];
    Dig[0][0] = 0;
    Dig[0][1] = 0;
    Dig[0][2] = 1;

    for (int i = 99; i > 1; i--) {
        int len = length(i);
        for (int k = 1; k <= len; k++) { // Sets up multiplication
            int pos = len - k;
            Dig[1][pos] = ((i / (int) (Math.pow(10, pos))) % 10);
        }
        int temp;
        for (int k = 0; k < len; k++) { // multiplication
            for (int j = 0; j < 159; j++) {
                Dig[2][k + j] += (Dig[1][k] * Dig[0][j]);
                if (Dig[2][k + j] >= 10) {
                    Dig[2][k + j + 1] += Dig[2][k + j] / 10;
                    Dig[2][k + j] = Dig[2][k + j] % 10;
                }
            }
        }
        sum = 0;
        for (int k = 159; k >= 0; k--) {
            System.out.print(Dig[2][k]);
            Dig[0][k] = Dig[2][k];
            Dig[1][k] = 0;
            sum += Dig[2][k];
            Dig[2][k] = 0;
        }
        System.out.println();
    }
    return sum;
}
0
Adhyyan Sekhsaria