web-dev-qa-db-ja.com

Java.math.BigIntegerを使用せずにJavaで非常に大きな数を処理する方法

Java.math.BigIntegerを使用せずに、任意の大きな整数で、算術+-/ *%!

たとえば、Javaでは、階乗90は0を返します。それを解決したいと思います。

61
frodosamoa

プログラマーは自分のbignum-libraryを一度実装すべきだったと思うので、ここで歓迎します。

(もちろん、後でBigIntegerの方が優れていることを理解し、これを使用しますが、貴重な学習体験です。)

(このコースライフのソースコードをフォローできます github 。また、これを(少し洗練された) 14部のブログシリーズ に作り直しました。)

Javaでの単純なBig numberクラスの作成

それで、何が必要ですか?

まず、数の表現、

Javaが提供するデータ型に基づきます。

10進数の変換は最も複雑な部分だと思うので、10進数ベースのモードのままにしてみましょう。効率のために、実際の10進数ではなく、ベース_1 000 000 000 = 10^9 < 2^30_で作業します。これは、Java int(最大_2^31_または_2^32_まで)と、そのような2つのdigitsの積Java long

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

次に、数字配列:

_private int[] digits;
_

数字をリトルエンディアンまたはビッグエンディアンで保存しますか?つまり、大きい部分を最初に保存しますか、それとも最後に保存しますか?それは実際には重要ではないので、人間がそれを読みたい方法であるため、ビッグエンディアンを決定します。 (ここでは、非負の値に集中します-後で負の数に符号ビットを追加します。)

テスト目的で、このような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();
}
_

追加のボーナスとして、このコンストラクターは単一のintBASEより小さい場合)にも使用でき、intなし(0と解釈します)にも使用できます。したがって、これを行うことができます。

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

これにより_de.fencing_game.paul.examples.DecimalBigInt@6af62373_が得られますが、それほど有用ではありません。そこで、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);
}
_

出力は_Big[7, 5, 2, 12345]_になりました。これはテストにより便利ですよね?

次に、10進形式からの変換。

ここでは幸運です。ベース(10 ^ 9)は、(10)から変換したいベースの累乗です。したがって、1つの「私たちの形式」の数字を表す同じ数(9)の10進数が常にあります。 (もちろん、最初は数桁少ないかもしれません。)次のコードでは、decimalは10進数の文字列です。

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

この奇妙な式は、Java intのbigLen = ceil(decLen/BASE_DECIMAL_DIGITS)の記述方法です。(正しいことを願っています。後でテストします。)

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

これは、10進数の最初のブロックの長さであり、1〜9の範囲である必要があります。

配列を作成します。

_ int[] digits = new int[bigLen];
_

作成される数字をループする:

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

ourの各数字は、元の数字の数字ブロックで表されます。

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

(ここで最初の短いブロックには_Math.max_が必要です。)通常の整数解析関数を使用して、結果を配列に入れます。

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

作成された配列から、DecimalBigIntオブジェクトを作成します。

_return new DecimalBigInt(digits);
_

これが機能するかどうか見てみましょう:

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

出力:

_Big[12, 345678901, 234567890]
_

正しく見えます:-)他の(長さの異なる)数値でもテストする必要があります。

次の部分は10進形式になりますが、これはさらに簡単です。

第三に、10進形式への変換。

個々の数字をそれぞれ9桁の10進数として出力する必要があります。このために、printfに似たフォーマット文字列をサポートするFormatterクラスを使用できます。

単純なバリアントは次のとおりです。

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

これは、2つの数値に対して_000000007000000005000000002000012345_および_000000012345678901234567890_を返します。これはラウンドトリップで機能します(つまり、valueOfメソッドに渡すと同等のオブジェクトが得られます)が、先頭のゼロは実際には見た目が良くありません(8進数で混乱を引き起こす可能性があります)。そのため、美しいfor-eachループを分解し、最初と次の数字に異なるフォーマット文字列を使用する必要があります。

_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();
}
_

添加。

これは単純なので、加算から始めましょう(そして、後で乗算にその一部を使用できます)。

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

式を読むのと同じように読めるメソッド名が必要です。したがって、plusminustimesではなく、addsubtractmultiplyです。

それでは、追加はどのように機能しますか?学校で学んだ9より大きい10進数の場合と同じように機能します。対応する数字を追加し、結果の一部が10(または、この場合はBASE)より大きい場合は、1を次の数字に運びます。これにより、結果の数値は元の数値よりも1桁多くなります。

最初に、両方の数字の桁数が同じである単純なケースを見てみましょう。次に、次のようになります。

_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);
_

(右から左に進むので、オーバーフローを次の桁に運ぶことができます。リトルエンディアン形式を使用することに決めた場合、これは少しきれいになります。)

両方の数字の桁数が同じでない場合、もう少し複雑になります。

できるだけ簡単にするために、いくつかのメソッドに分割します。

このメソッドは、配列の要素(ゼロ以外の値が既に含まれている場合があります)に1桁を追加し、結果を配列に保存します。オーバーフローが発生した場合、再帰呼び出しによって次の桁(1つ以上ではなく、1つ少ないインデックス)にそれを運びます。このようにして、数字が常に有効な範囲に収まるようにします。

_/**
 * 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);
    }
}
_

次に、追加する数字の配列全体に対して同じことを行います。

_/**
 * 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--;
    }
}
_

これで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);
}
_

オーバーフローが可能な場合は前を見て、必要なものよりも大きい配列を作成するだけで、ここで少し改善できます。

ああ、1つのテスト:d2.plus(d2)は_Big[24, 691357802, 469135780]_を与えます。

乗算。

学校に戻って覚えておきましょう、紙の上でより大きな数字をどのように増やしましたか?

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

そのため、最初の数値の各digit [i]を2番目の数値の各digit [j]で乗算し、結果のdigit [i + j]に積を加算する必要があります(そして、キャリーに注意してください)。もちろん、ここではインデックスは左からではなく右からカウントされます。 (これで、リトルエンディアンの数字を使用したかったのですが)

2つの数字の積はintの範囲外になる可能性があるため、乗算にはlongを使用します。

_/**
 * 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);
}
_

これで、addDigitsメソッドを宣言してresultIndexパラメーターを使用する理由を確認できます。 (そして、ここでこれをより適切に記述できるように、最後の引数をvarargsパラメーターに変更しました。)

それで、ここでクロス乗算法:

_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]);
        }
    }
}
_

インデックス計算が正しいことを願っています。リトルエンディアン表現では、それはmultiplyDigit(result, resultIndex + i + j, leftFactor[i], rightFactor[j])になっていたはずです-非常に明確ですよね?

timesメソッドは、結果配列を割り当て、multiplyDigitsを呼び出し、結果をラップするだけです。

_/**
 * 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);
}
_

テストのために、d2.times(d2)は_Big[152, 415787532, 388367501, 905199875, 19052100]_を返します。これは、私のEmacs calcがここで計算するものと同じです。

比較

2つのオブジェクトを比較できるようにしたいと思います。そこで、_Comparable<DecimalBigInt>_とそのcompareToメソッドを実装します。

_public int compareTo(DecimalBigInt that) {
_

ある数値が他の数値よりも大きいかどうかを知る方法は?最初に、配列の長さを比較します。先行ゼロを誘導しないように注意したため(我々はそうしましたか?)、長い配列ほど大きな数を持つ必要があります。

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

長さが同じ場合、要素ごとに比較できます。ビッグエンディアン(つまり、ビッグエンドが最初に来る)を使用するため、最初から始めます。

_    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;
        }
    }
_

すべてが同じであれば、明らかに私たちの数は同じであり、_0_を返すことができます。

_    return 0;
}
_

equals + hashCode()

すべての適切な不変クラスは、equals()およびhashCode()を適切な(および互換性のある)方法で実装する必要があります。

hashCode()の場合、数字を単純に合計し、数字に小さな素数を掛けて、数字の切り替えが同じハッシュコードにならないようにします。

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

equals()メソッドでは、同じアルゴリズムを再度実装する代わりに、単にcompareToメソッドに委任できます。

_/**
 * 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;
}
_

それで、今日はこれで十分です。減算(およびおそらく負の数)と除算はより複雑なので、ここでは省略します。 90の階乗を計算するにはこれで十分です

大きな階乗の計算:

ここで階乗関数:

_/**
 * 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;
}
_

これは私たちに与えます

_fac(90) = 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000
_

任意基数表現からの変換

フロドサモアの次の質問に促されて、私は 計算可能な(または計算したい)任意の(位置)数値システムから変換する方法についての私の答え を書きました。 (そこの例では、3進数から10進数に変換しましたが、質問は10進数から2進数についてでした。)

ここで、任意の数値システム(2〜36の基数を持つ大丈夫ですから、 Character.digit() を使用して1桁をintに変換できます)を基数を持つシステムに変換します。 BASE(= 1.000.000.000、ただし、これはここではあまり重要ではありません)。

基本的に、 ホルナースキーム を使用して、基数で指定されたポイントで係数として桁を持つ多項式の値を計算します。

_sum[i=0..n] digit[i] * radix^i
_

このループで計算できます:

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

入力文字列はビッグエンディアンなので、カウントダウンする必要はありませんが、単純な拡張forループを使用できます。 (Javaでは演算子のオーバーロードがなく、intからDecimalBigInt型へのオートボクシングも行われないため、Javaの方が見苦しくなります。)

_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;
}
_

私の実際の実装 に、エラーチェック(および例外のスロー)を追加して、実際に有効な番号と、もちろんドキュメントコメントがあることを確認しました。


からへの変換は、まだ実装していない剰余と除算(任意の基数による)を含むため、より複雑です。今のところ。除算の方法について良いアイデアを持っているときに行われます。 (ここでは、小さな(1桁の)数値による除算のみが必要です。これは、一般的な除算よりも簡単な場合があります。)

少数による除算

学校で、私は long division を学びました。ここでは、ドイツでここで使用している表記(通常は記述しないバックグラウンド計算に関する注釈付き)の小さな(1桁の)除数の例を10進法で示します。

_ 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.
_

当然、これらの積(0、12、0、30、42)を計算し、ネイティブの剰余演算がある場合はそれらを減算する必要はありません。そうすると、次のようになります(もちろん、ここで操作を記述する必要はありません)。

_ 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.
_

別の形式で記述した場合、これはすでに short division のように見えます。

以下を観察(および証明)できます。

除数dよりも小さい最初の桁を持つ2桁の数値xがある場合、_x / d_は1桁の数値であり、_x % d_もdより小さい1桁の数値です。これは、帰納法とともに、除数で2桁の数値を(余りで)除算するだけでよいことを示しています。

基数BASEを使用して大きな数値に戻ると、2桁の数値はすべてJava longとして表現できます。ネイティブの_/_と_%_があります。

_/**
 * 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;
}
_

このメソッドをループで呼び出し、常に前の呼び出しの結果を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;
}
_

このメソッドは、残りのintを返します。

今度は、DecimalBigIntを返すパブリックメソッドが必要なので、作成します。引数を確認し、作業メソッドの配列を作成し、残りを破棄し、結果からDecimalBigIntを作成するタスクがあります。 (コンストラクタは、そこにある可能性のある先行ゼロを削除します。)

_/**
 * 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);
}
_

代わりに剰余を返す同様のメソッドもあります:

_/**
 * 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);
}
_

これらのメソッドは、次のように呼び出すことができます。

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

任意の基数への変換

これで、任意の基数に変換するための基本ができました。もちろん、arbitrary意的ではなく、BASEより小さい基数のみが許可されますが、これはそれほど大きな問題ではありません。

数値の変換に関する別の回答で既に答えたように、「除算、剰余、乗算、加算」を行う必要があります。「乗算-加算」部分は実際には個々の数字をまとめるだけなので、単純な配列に置き換えることができます。アクセス。

商と剰余の両方が常に必要なので、パブリックメソッドmodulodivideByを使用せず、代わりに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!");
    }
_

最初に、0の特別な場合の処理​​。

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

次に、結果の数字(十分な長さ)およびその他の変数の配列を作成します。

_    // 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は、最後の商の桁数(先行ゼロを除く)です。これが0であれば、完了です。

_    while(quotLen > 0)  {
_

次の商の新しい配列。

_        int[] quot = new int[quotLen];
_

商と剰余の演算。商は現在quotにあり、残りはremにあります。

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

残りを出力配列に入れます(最後の桁から埋めます)。

_        rDigits[rIndex] = rem;
        rIndex --;
_

次に、次のラウンドのために配列を交換します。

_        current = quot;
_

商に先行ゼロがある場合(基数がBASEより小さいため、最大で1つあります)、商のサイズを1つ縮小します。次の配列は小さくなります。

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

ループの後、rDigits配列に先行ゼロが存在する可能性があるため、それらを切り捨てます。

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

それでおしまい。ただし、少し複雑に見えます。使用方法の例を次に示します。

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

これらは_[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]_および_[1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0]_を出力しますが、以前に解析したのと同じ数字です(ただし、Stringから)。

これに基づいて、文字列としてフォーマットすることもできます。

_/**
 * 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

BigIntegerを避けたい場合は、 binary-coded decimal のライブラリを実装または調査することをお勧めします。ただし、使用する場合は、BigIntegerを使用して90の階乗を達成できます。

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

以下のコードを使用して、任意の長さの数値を乗算します。

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

Java演算子+-*/、および%を使用した算術演算は、 Javaプリミティブデータ型 の制約。

これは、たとえばdoubleまたはlongの範囲に希望の数値を収めることができない場合、次のような「大きな数値」ライブラリを使用する必要があることを意味します。 Java( BigDecimalBigInteger )、またはサードパーティライブラリへの組み込み、または独自の記述。これは、 Javaは演算子のオーバーロードをサポートしていないため、算術演算子は使用できません。

1
maerics

算術演算を実行したい本当に大きな数がある場合、それらは文字列などのオブジェクト形式でなければなりません。

それらをBigIntegerの範囲よりも長い文字長の文字列とします。

この場合、ノートブックで行うのと同じ方法で算術演算を実行します。例-追加を行う必要があると仮定しましょう。 2つの文字列の長さを比較することから始めます。 3つの新しい文字列を作成します。最初の文字列は小さい方です。 2番目の文字列は、長い文字列の右端の部分文字列で、長さが小さい文字列に等しくなります。 3番目の文字列は、左側からの残りの長い文字列です。次に、文字を整数に変換し、キャリーをint変数に保持して、文字列を最後から1番目と2番目の文字列に追加します。各追加の直後に、StringBufferに合計を追加します。 2つの文字列を追加したら、3番目の文字列に対して同じ操作を行い、キャリーを追加し続けます。最後に、StringBufferを逆にして文字列を返します。

加算に使用したコードは次のとおりです

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

strong text 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

90をやりたいとき!または他の大規模な計算では、各要素が数字の1つを保持するint []配列を使用しようとします。次に、ペンと紙を使用して従来の乗算を適用し、別のint []配列で答えを取得します。

これは、私がJavaで書いたコードで、100!をかなり速く計算します。好きなように使用してください。

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