Как обрабатывать очень большие числа в Java без использования java.математика.BigInteger
Как я буду делать арифметику,+ -/ * % !, с произвольно большими целыми числами без использования java.math.BigInteger?
например, факториал 90 возвращает 0 в Java.
Я хотел бы иметь возможность решить эту проблему.
7 ответов:
я думаю, что программист должен был реализовать свою собственную библиотеку bignum один раз, так что добро пожаловать сюда.
(конечно, позже вы получите, что BigInteger лучше, и использовать это, но это ценный опыт обучения.)
(вы можете следить за исходным кодом этого курса жизни на github. Кроме того, я переделал это (немного отполированный) в 14-часть серии блог.)
создание простого класса большого числа в Java
Итак, что нам нужно?
во-первых, представление числа,
на основе типов данных, которые дает нам Java.
как вы думаете, десятичное преобразование является наиболее сложной частью, давайте останемся в десятичном режиме на основе. Для эффективности мы будем хранить не реальные десятичные цифры, а работать в базе
1 000 000 000 = 10^9 < 2^30. Это вписывается в Javaint(до2^31или2^32), и произведение двух таких цифры подходит красиво в Javalong.final static int BASE = 1000000000; final static int BASE_DECIMAL_DIGITS = 9;затем цифры-массив:
private int[] digits;мы храним цифры в мало - или обратным порядком байтов, т. е. чем больше частей первой или последней? На самом деле это не имеет значения, поэтому мы решаем на big-endian, так как это то, как люди хотят его читать. (Сейчас мы сосредоточимся на неотрицательных значениях - позже мы добавим знаковый бит для отрицательных чисел.)
для целей тестирования мы добавляем конструктор, который позволяет инициализировать из такого 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(); }в качестве дополнительного бонуса, этот конструктор также можно использовать для одного
int(если меньшеBASE), и даже ни за что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^9) - это сила базы, которую мы хотим преобразовать из (10). Таким образом, у нас всегда есть одно и то же число (9) десятичных цифр, представляющих одну цифру "нашего формата". (Конечно, в начале может быть несколько цифр меньше.) В следующем коде
decimal- это строка десятичных цифр.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;это длина первого блока десятичных цифр, должно быть между 1 и 9 (включительно).
мы создаем наш массив:
int[] digits = new int[bigLen];цикл через цифры, которые будут созданы:
for(int i = 0; i < bigLen ; i++) {каждого наши цифры представлены блоком цифр в исходном номере:
String block = decimal.substring(Math.max(firstSome + (i-1)*BASE_DECIMAL_DIGITS, 0), firstSome + i *BASE_DECIMAL_DIGITS);(The
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]выглядит правильно :-) мы должны проверить его с некоторыми другими числами (разной длины) тоже.
Следующая часть будет десятичное форматирование, это должно быть еще проще.
в-третьих, преобразование в десятичное число формат.
мы должны вывести наши отдельные цифры в виде 9 десятичных цифр каждый. Для этого мы можем использовать
Formatterкласс, который поддерживает printf-подобные строки формата.простой вариант будет такой:
public String toDecimalString() { Formatter f = new Formatter(); for(int digit : digits) { f.format("%09d", digit); } return f.toString(); }возвращает
000000007000000005000000002000012345и000000012345678901234567890для наших двух чисел. Это работает для поездки туда и обратно (т. е. кормление егоvalueOfметод дает эквивалентный объект), но ведущие нули не очень приятно смотреть (и может создать путаница с восьмеричными числами). Поэтому нам нужно разбить наш красивый цикл 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) { ... }я хочу имена методов, которые вы можете прочитать, как вы читали бы формулу, таким образом
plus,minus,timesвместоadd,subtract,multiply.Итак, как работает добавление? Он работает так же, как мы учили его в школе для десятичных чисел выше 9: добавьте соответствующие цифры, и если для некоторых из них результат больше 10 (или
BASEв нашем случае), перенесите один на следующую цифру. Это может привести к тому, что полученное число будет иметь на одну цифру больше, чем исходные.Сначала мы рассмотрим простой случай, что оба числа имеют одинаковое количество цифр. Тогда это выглядит просто вот так:
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);(мы идем справа налево, поэтому мы можем переносить любые переполнения на следующую цифру. Это было бы немного красивее, если бы мы решили использовать маленький формат Endian.)
если оба числа не имеют одинаковое количество цифр, это становится немного сложнее.
чтобы это было как можно проще, мы разделили его на несколько методов:
этот метод добавляет одну цифру к элементу в массиве (который может уже содержать некоторые ненулевое значение), и сохраняет результат обратно в массив. Если произошло переполнение, мы переносим его на следующую цифру (которая имеет индекс на один меньше, а не на один больше) с помощью рекурсивного вызова. Таким образом, мы гарантируем, что наши цифры всегда остаются в допустимом диапазоне.
/** * 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); }мы могли бы сделать немного лучше здесь, если бы мы смотрели раньше если переполнение вообще возможно и только тогда создать массив один больше, чем необходимо.
Ах, один тест:
d2.plus(d2)даетBig[24, 691357802, 469135780], который смотрит прямо.умножение.
давайте вспомним еще в школе, как мы умножали большие числа на бумаге?
123 * 123 ---------- 369 <== 123 * 3 246 <== 123 * 2 123 <== 123 * 1 -------- 15129Итак, мы должны умножить каждую цифру[i] первого числа с каждой цифрой[j] второго числа и добавить продукт в цифру[i+j] результата (и заплатить внимание к себе). Конечно, здесь индексы отсчитываются справа, а не слева. (теперь я действительно жалею, что не использовал числа с малым концом.)
так как произведение двух наших цифр может выйти за пределы диапазона
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метод, чтобы взять
вы можете реализовать или исследовать библиотеку для двоично-десятичный если вы пытаетесь избежать
BigInteger. Вы можете выполнить факториал 90 сBigIntegerЕсли вы хотите использовать его, Хотя: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; }
арифметические операции в Java с помощью операторов
+,-,*,/и%связаны ограничениями примитивные типы данных Java.это означает, что если вы не можете вписать нужные числа в диапазон, скажем a
doubleилиlongтогда вам придется использовать библиотеку" большого числа", такую как встроенная в Java (BigDecimal,BigInteger), или стороннюю библиотеку, или написать свой собственный. Это также означает, что нельзя использовать арифметические операторы, так как Java не поддерживает перегрузку операторов.
используйте приведенный ниже код для умножения чисел любой длины: -
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)); } }
сильный текст 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)); }}
когда я хочу 90! или какой-то другой массивный расчет, я пытаюсь использовать массив 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; }
Если у нас есть действительно большие числа, на которых мы хотим выполнять арифметические операции, чем они должны быть в какой-то форме объекта, такие как строки.
пусть это строки с длиной символа больше диапазона BigInteger.
в этом случае я буду выполнять арифметические операции, как мы делаем это на ноутбуке. Например - предположим, что мы должны сделать дополнение. Начните с сравнения двух строк по длине. Три новые строки. Первая строка - это менее крупный. Вторая строка является самой правой подстрокой более длинной строки с длиной, равной меньшей строке. Третья строка-это оставшаяся длинная строка с левой стороны. Теперь добавьте первую и вторую строку из конца, Преобразуя символы в целые числа, по одному символу за раз и сохраняя перенос в переменной int. Сразу после каждого сложения, добавьте сумму в StringBuffer. После добавления двух строк выполните ту же операцию для третьей строки и продолжайте добавлять ношение. В конце переверните 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; }
Comments