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

Comment pourrais-je aller sur le faire de l'arithmétique, + - / * % !, avec des entiers arbitrairement grands sans utiliser java.math.BigInteger ?

par exemple, le factoriel de 90 renvoie 0 en Java. Je voudrais être en mesure de résoudre.

57
demandé sur Daryl Bennett 2011-03-16 00:06:08

7 réponses

je pense qu'un programmeur aurait dû implémenter sa propre bibliothèque bignum une fois, donc bienvenue ici.

(bien sûr, plus tard, vous obtiendrez que BigInteger est mieux, et utilisez ceci, mais c'est une expérience d'apprentissage précieuse.)

(vous pouvez suivre le code source de cette vie de cours sur github . En outre, je remade ceci (un peu poli) dans un 14-partie de la série de blog .)

la Création d'un simple Grand nombre de classe en Java

Alors, de quoi avons-nous besoin?

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 en mode décimal. Pour plus d'efficacité, nous ne stockerons pas de vrais chiffres décimaux, mais travaillerons dans la base 1 000 000 000 = 10^9 < 2^30 . Cela s'inscrit dans un Java int (jusqu'à 2^31 ou 2^32 ), et le produit de deux tels chiffres s'adapte bien dans une Java long .

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

puis les chiffres-tableau:

private int[] digits;

est - ce que nous stockons les chiffres en petit-ou grand endian, c'est-à-dire les plus grandes parties en premier ou en dernier? Cela n'a pas vraiment d'importance, donc nous décidons sur big-endian puisque c'est ainsi que les humains veulent le lire. (Pour l'instant, nous nous concentrons sur les valeurs-plus tard, nous ajouterons un bit de signe pour les nombres négatifs.)

pour les tests, 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();
}

comme bonus supplémentaire, ce constructeur est également utilisable pour un seul int (si plus petit que BASE ), et même pour le no int (que nous interpréterons comme 0). Ainsi, 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. Ainsi, nous ajoutons une toString() méthode:

/**
 * 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 tester, n'est-ce pas?

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

nous sommes chanceux ici: notre base (10^9) est une puissance de la base que nous voulons convertir de (10). Ainsi, nous avons toujours le même nombre (9) de décimales représentant un chiffre "notre format". (Bien sûr, au début, il n' peut-être certains chiffres moins.) 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 écriture Java int 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;

longueur du premier bloc de chiffres décimaux comprise entre 1 et 9 (inclusivement).

nous créons notre tableau:

 int[] digits = new int[bigLen];

Boucle à travers 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 original:

    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 de parsing entier, et mettons le résultat dans le tableau:

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

à partir du tableau maintenant créé, nous créons notre Décimalbigint objet:

return new DecimalBigInt(digits);

voyons si cela fonctionne:

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

sortie:

Big[12, 345678901, 234567890]

semble à droite: -) nous devrions le tester avec d'autres nombres (de longueur différente) aussi.

la prochaine partie sera le formatage décimal, cela devrait être encore plus facile.

troisième, conversion au format décimal.

nous devons produire notre individu chiffres en 9 chiffres décimaux chacun. Pour cela, nous pouvons utiliser la classe Formatter , qui supporte les chaînes de format de type printf.

une variante simple serait celle-ci:

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

cela renvoie 000000007000000005000000002000012345 et 000000012345678901234567890 pour nos deux numéros. Cela fonctionne pour un aller-retour (i.e. l'alimenter à la méthode valueOf donne un objet équivalent), mais les zéros de tête ne sont pas vraiment agréable à regarder (et pourrait créer une confusion avec octal nombre.) 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();
}

Addition.

commençons par l'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 pouvez lire comme vous liriez la formule, donc plus , minus , times au lieu de add , subtract , multiply .

alors, comment fonctionne l'addition? Il fonctionne de la même façon que nous l'avons appris à l'école pour les nombres décimaux supérieurs à 9: ajouter les chiffres correspondants, et si pour certains alors le résultat est plus grand que 10 (ou BASE dans notre cas), porter un au chiffre suivant. Cela peut causer le nombre résultant d'avoir un chiffre de plus que les originaux.

cas où les deux numéros ont le même nombre de chiffres. Alors il ressemble tout 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, donc nous pouvons reporter tout débordement 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, il devient un peu plus compliqué.

pour faire aussi simple que possible, nous le divisons en plusieurs méthodes:

Cette méthode permet d'ajouter un chiffre à un élément du tableau (qui peut déjà contenir une valeur non nulle), et stocke le résultat dans le tableau. S'il y avait débordement, nous le portons au chiffre suivant (qui a un indice de moins, pas un de plus) au moyen d'un appel récursif. De cette façon, nous nous assurons que nos chiffres de rester 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);
    }
}

le suivant fait la même chose pour un tableau entier 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--;
    }
}

maintenant nous pouvons 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 regardons avant si le débordement est tout à fait possible et seulement alors créer le tableau un plus grand que nécessaire.

AH, un test: d2.plus(d2) donne Big[24, 691357802, 469135780] , qui semble droit.

Multiplication.

souvenons-nous de retourner à l'école, comment avons-nous multiplié plus de chiffres sur le papier?

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

ainsi, nous devons multiplier chaque chiffre[i] du premier numéro avec chaque chiffre[j] du deuxième numéro, et ajouter le produit dans le chiffre[i+j] du résultat (et faire attention à porter). Bien sûr, ici, les indices sont comptés à partir de la droite, pas de gauche. (maintenant j'aurais vraiment aimé avoir utilisé des petits nombres-endian.)

puisque le produit de deux de nos chiffres peut sortir de la gamme 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);
}

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

ainsi, ici la méthode de la 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 que j'ai bien calculé l'indice. Avec un petit-endian représentation, ça aurait été multiplyDigit(result, resultIndex + i + j, leftFactor[i], rightFactor[j]) - plus clair, n'est-ce pas?

notre méthode times n'a plus qu'à attribuer 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 les essais, d2.times(d2) donne Big[152, 415787532, 388367501, 905199875, 19052100] , qui est le même que mon Emacs calc calcule ici.

comparaison

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

public int compareTo(DecimalBigInt that) {

Comment savoir si un de nos nombres est plus grand qu'un autre? Tout d'abord, nous comparons la longueur des tableaux. Comme nous avons pris soin de ne pas induire des zéros (avons-nous?), le long de tableau 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 la longueur est la même,nous pouvons comparer les éléments. Puisque nous utilisons big endian (i.e. la grande fin vient en premier ), nous commençons par le commencement.

    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 pareil, évidemment nos nombres sont identiques, et nous pouvons retourner 0 .

    return 0;
}

equals + hashCode()

toute bonne classe immuable doit mettre en œuvre equals() et hashCode() d'une manière appropriée (et compatible).

pour notre hashCode() , nous résumons simplement les chiffres, en les multipliant par un petit prime pour s'assurer que le changement de chiffre n'entraîne 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 tout simplement déléguer à la méthode compareTo, au lieu d'implémenter à nouveau 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 les nombres négatifs) et la division sont plus compliquées, donc je les Omets pour le moment. pour le calcul de la factoriel de 90 cela devrait suffire.

Calcul de gros factorielles:

ici 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 de représentations arbitraires-radix

poussé par la question suivante de frodosamoa, j'ai écrit ma réponse sur la façon de convertir de nombre arbitraire (de position) systèmes dans lesquels nous pouvons (OU voulons) calculer . (Dans l'exemple, j'ai converti de trinaire à décimal, alors que la question était d'environ décimal À binaire.)

ici nous voulons convertir d'un système de nombre arbitraire (d'accord, avec radix entre 2 et 36, donc nous pouvons utiliser Character.digit() pour convertir des chiffres simples en ints) à notre système avec radix BASE (=1.000.000.000, mais ce n'est pas vraiment important ici).

Fondamentalement, nous utilisons Horner scheme pour calculer la valeur du polynôme avec les chiffres comme coefficients au point donné par le radix.

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

étant donné que nos chaînes de saisie sont big-endian, nous n'avons pas à compter vers le bas, mais pouvons utiliser un simple amélioré pour boucle. (Il semble plus laid en Java, puisque nous n'avons aucun opérateur en surcharge, et pas d'autoboxing de l'int à notre Type décimalbigint.)

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 effective j'ai ajouté un peu de vérification des erreurs (et à l'exception de jeter) pour s'assurer que nous avons vraiment un numéro de série valide, et bien sûr un commentaire de documentation.


conversion en un système de position arbitraire est plus compliqué, car il implique le reste et la division (par l'arbitraire radix), que nous n'avons pas les mettre en œuvre - donc pas pour l'instant. Ce sera fait quand j'aurai une bonne idée de comment faire la division. (Nous n'avons besoin ici que d'une division par de petits nombres (un chiffre), ce qui peut être plus facile qu'une division générale.)

Division par petits nombres

à l'école, j'ai appris longue division . Voici un exemple pour un petit (à un chiffre) diviseur, dans la notation que nous utilisons ici en Allemagne (avec annotations sur les calculs de fond, que nous n'écririons normalement pas), 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.

de Cuse, nous n'avons pas besoin de calculer ces produits (0, 12, 0, 30, 42) et soustraire si nous avons un natif reste de l'opération. Puis il ressemble comme 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 écrivons dans un autre format.

nous pouvons observer (et prouver) ce qui suit:

si nous avons un nombre à deux chiffres x avec le premier chiffre plus petit que notre diviseur d, x / d est un nombre à un chiffre, et x % d est aussi un nombre à un chiffre plus petit que D. Ceci, avec l'induction, montre que nous n'avons besoin que de diviser (avec le reste) les nombres à deux chiffres par notre diviseur.

revenant à nos grands nombres avec base radix: tous les numéros à deux chiffres peuvent être représentés comme un Java long , et là nous avons natif / et % .

/**
 * 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 dans une boucle, toujours nourrir le résultat de l'appel précédent comme 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 renvoie encore un int, le reste.

maintenant, nous voulons avoir une méthode publique retournant un caractère décimal, donc nous en créer un. Il a la tâche de vérifier les arguments, de créer un tableau pour la méthode de travail, de jeter le reste, et de créer une Décimalbigint à partir du résultat. (Le constructeur supprime un zéro principal 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 aussi une méthode similaire, qui retourne 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 invoqué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

Maintenant, nous avons les bases pour convertir arbitraire radix. Bien sûr, pas vraiment arbitraire, seuls les rayons plus petits que BASE sont autorisés, mais cela ne devrait pas être un trop gros problème.

comme déjà répondu dans une autre réponse sur la conversion des nombres, nous devons faire" division, reste, Multiplier, Ajouter. La partie" Multiplier-Ajouter " n'est en fait que la compilation des différents chiffres, de sorte que nous pouvons la remplacer par un simple tableau-accès.

comme nous avons toujours besoin à la fois du quotient et du reste, nous n'utiliserons pas les méthodes publiques modulo et divideBy , mais appellerons plutôt à 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!");
    }

D'abord, un cas spécial de manipulation pour 0.

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

ensuite, nous créons un tableau pour les chiffres de 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'exclusion des zéros) dans le dernier quotient. Si c'est 0, on a fini.

    while(quotLen > 0)  {

un nouveau tableau pour le quotient suivant.

        int[] quot = new int[quotLen];

Le quotient et le reste de l'opération. Le quotient est maintenant en quot , le reste dans rem .

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

nous mettons le reste dans le tableau de sortie (le remplir à partir du dernier chiffre).

        rDigits[rIndex] = rem;
        rIndex --;

puis on échange les tableaux pour le tour suivant.

        current = quot;

S'il y a des zéros de tête dans le quotient (il y en aura au plus un, car radix est plus petit que BASE), nous réduisons la taille du quotient par un. La matrice suivante sera plus petite.

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

après la boucle il peut y avoir des zéros de tête dans le tableau de rDigits, et nous les Coupons.

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

C'est ça. Il ressemble un peu compliqué, bien. 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)));

ces caractères [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] , juste les mêmes nombres que nous avons parsés avant (à partir d'une chaîne, cependant).

sur cette base nous pouvons aussi formater comme une chaîne de caractères:

/**
 * 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();
}
238
répondu Paŭlo Ebermann 2017-05-23 12:18:14

vous pourriez vouloir mettre en œuvre ou rechercher une bibliothèque pour décimal binaire codé si vous essayez d'éviter BigInteger . Vous pouvez accomplir factoriel de 90 avec BigInteger si vous voulez l'utiliser cependant:

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
répondu WhiteFang34 2011-03-15 22:42:47

opérations arithmétiques en Java utilisant les opérateurs + , - , * , / , et % sont liés par les contraintes des Java primitive data types .

cela signifie que si vous ne pouvez pas ajuster vos nombres désirés dans la gamme de, disons un double ou long alors vous devrez utiliser un" grand nombre " bibliothèque, comme celui intégré à 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 puisque Java ne supporte pas la surcharge des opérateurs.

1
répondu maerics 2011-03-15 21:24:19

utilisez le code ci-dessous pour multiplier les nombres de n'importe quelle longueur: -

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
répondu user2130532 2014-09-22 07:45:04

texte fort "151930920 public" la classe 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
répondu BBsal 2015-09-12 01:33:54

quand je veux 90! ou un autre calcul massif, j'essaie d'utiliser un tableau int [], chaque élément contenant un des chiffres. Ensuite j'applique la multiplication traditionnelle que nous utilisons stylo et papier pour obtenir la réponse dans un autre tableau int [].

C'est le code que J'ai écrit en Java qui calcule 100! plutôt rapidement. N'hésitez pas à utiliser ceci, toutefois, vous aimez.

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
répondu Adhyyan Sekhsaria 2016-06-17 17:41:58

si nous avons vraiment de grands nombres sur lesquels nous voulons effectuer des opérations arithmétiques qu'ils doivent être dans une forme d'objet comme les chaînes.

laisser leurs chaînes de be avec la longueur de caractère plus grande que la gamme de BigInteger.

dans ce cas, je ferai une opération arithmétique comme sur un ordinateur portable. Par exemple-supposons que nous devons faire l'addition. Commencez par comparer les deux chaînes pour la longueur. Effectuer trois nouvelles Chaîne. La Première Chaîne est la plus petite. La seconde chaîne est la chaîne la plus droite de la chaîne la plus longue avec une longueur égale à la plus petite chaîne. La troisième corde est la longue corde restante du côté gauche. Maintenant, ajoutez la première et la deuxième chaîne de caractères à partir de la fin de conversion des caractères en entiers, un caractère à la fois et de garder le carry dans une variable int. Immédiatement après chaque addition, ajoutez la somme dans un fichier StringBuffer. Après l'ajout des deux chaînes, effectuez la même opération pour le troisième corde et continuer à ajouter le carry. À la fin, inversez le batteur de cordes et retournez la corde.

voici le code que j'ai utilisé pour ajouter

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
répondu Shubhdeep Singh 2018-04-04 05:59:30