Я думаю, что программист должен был реализовать собственную библиотеку bignum один раз, поэтому добро пожаловать сюда.
(Конечно, позже вы поймете, что BigInteger лучше, и воспользуетесь этим, но это ценный опыт обучения.)
(Вы можете следить за исходным кодом этого курса life на github . Также я переделал это (немного отточенное) в серию блогов из 14 частей .)
Создание простого класса больших чисел в Java
Итак, что нам нужно?
Во-первых, представление числа,
на основе типов данных, которые нам дает Java.
Поскольку вы думаете, что десятичное преобразование является наиболее сложной частью, давайте останемся в десятичном режиме. Для эффективности мы будем хранить не реальные десятичные цифры, а работать в базе 1 000 000 000 = 10^9 < 2^30
. Это подходит для Java int
(до 2^31
или 2^32
), а произведение двух таких цифр прекрасно подходит для Java long
.
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);
(Math.max
необходим здесь для первого более короткого блока.)
Теперь мы используем обычную функцию разбора Integer и помещаем результат в массив:
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);
(Мы идем справа налево, поэтому мы можем перенести любые переполнения на следующую цифру. Это было бы немного красивее, если бы мы решили использовать формат Little 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
принимает параметр 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 здесь.
Сравнение
Мы хотим иметь возможность сравнивать два наших объекта. Итак, мы реализуем Comparable<DecimalBigInt>
и его метод сравнения.
public int compareTo(DecimalBigInt that) {
Как узнать, один из наших номеров больше другого? Сначала мы сравним длину массивов. Поскольку мы позаботились о том, чтобы не приводить никаких ведущих нулей (не так ли?), Более длинный массив должен иметь большее число.
if(this.digits.length < that.digits.length) {
return -1;
}
if (that.digits.length < this.digits.length) {
return 1;
}
Если длина одинакова, мы можем сравнивать поэлементно. Так как мы используем big endian (т.е. большой конец идет первым ), мы начинаем с начала.
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()
мы просто можем делегировать методу сравнения, вместо того, чтобы снова реализовать тот же алгоритм:
/**
* 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
Преобразование из произвольно-радикальных представлений
В ответ на следующий вопрос о frodosamoa я написал мой ответ о том, как преобразовать из произвольных (позиционных) систем счисления в однув котором мы можем (или хотим) рассчитать .(В приведенном здесь примере я перешел из трины в десятичную, в то время как вопрос был о десятичной в двоичную.)
Здесь мы хотим преобразовать из произвольной системы счисления (хорошо, с основанием от 2 до 36, поэтомумы можем использовать Character.digit()
для преобразования однозначных чисел в целые числа) в нашу систему с помощью radix 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.)
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;
}
В моей реальной реализации Я добавил некоторую проверку ошибок(и исключение), чтобы убедиться, что у нас действительно есть действительное число, и, конечно, комментарий к документации.
Преобразование в произвольной позиционной системы более сложное, так как включает в себяостаток и деление (по произвольному основанию), которые мы еще не реализовали - так что не сейчас.Это будет сделано, когда у меня будет хорошая идея о том, как сделать разделение.(Здесь нужно только деление на маленькие (однозначные) числа, что может быть проще, чем общее деление.)
Деление на маленькие числа
В школе я выучил longраздел .Вот пример для небольшого (однозначного) делителя в обозначениях, которые мы используем здесь, в Германии (с аннотациями о фоновых вычислениях, которые мы обычно не пишем), в десятичной системе:
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.
Это уже выглядит совсем как короткое деление , если мы напишем его в другом формате.
Мы можем наблюдать (и доказать) следующее:
Если у нас есть двузначное число x с первой цифрой, меньшей нашего делителя d, то x / d
является одной цифройчисло, и x % d
также является однозначным числом, меньшим, чем d.Это вместе с индукцией показывает, что нам когда-либо нужно делить (с остатком) двузначные числа на наш делитель.
Возвращаясь к нашим большим числам с основанием BASE: все двузначные числа представляются в виде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));
Преобразование в произвольное основание
Теперь у нас есть основы для преобразования в произвольное основание. Конечно, на самом деле не произвольно, допускаются только радиусы меньше BASE
, но это не должно быть слишком большой проблемой.
Как уже отвечали в другом ответе о преобразовании чисел, мы должны сделать «деление, остаток, умножение, сложение». Часть «умножение-сложение» фактически только собирает отдельные цифры, поэтому мы можем заменить ее простой доступ к массиву.
Поскольку нам всегда нужны как частное, так и остаток, мы не будем использовать открытые методы modulo
и divideBy
, а вместо этого повторно будем вызывать метод 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;
Если в частном есть начальные нули (их будет максимум один, так как
radix меньше, чем BASE), мы уменьшаем частный размер на единицу. Следующий массив
будет меньше.
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]
, те же самые числа, которые мы анализировали ранее (хотя из строки).
На основании этого мы также можем отформатировать строку:
/**
* 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();
}