How would I go about doing arithmetic, + - / * % !, with arbitrarily large integers without using java.math.BigInteger
?
如果不使用java.math.BigInteger,我如何使用任意大整数进行算术,+ - / * % ! ?
For instance, the factorial of 90 returns 0 in Java. I would like to be able to solve that.
例如,90的阶乘在Java中返回0。我希望能够解决这个问题。
7 个解决方案
#1
237
I think a programmer should have implemented his own bignum-library once, so welcome here.
我认为一个程序员应该实现他自己的bignum-library一次,所以欢迎来到这里。
(Of course, later you'll get that BigInteger is better, and use this, but it is a valuable learning experience.)
(当然,稍后您会得到BigInteger是更好的,并使用它,但这是一种宝贵的学习经验。)
(You can follow the source code of this course life on github. Also, I remade this (a bit polished) into a 14-part blog series.)
(你可以参考github上本课程的源代码。此外,我还把这个(稍微润饰一下)重做了一个包含14个部分的博客系列。
Creating a simple Big number class in Java
So, what do we need?
那么,我们需要什么呢?
First, a representation of the number,
based on the datatypes which Java gives us.
基于Java提供的数据类型。
As you think the decimal conversion is the most complicated part, let's stay in a decimal based mode. For efficiency, we'll store not real decimal digits, but work in base 1 000 000 000 = 10^9 < 2^30
. This fits in a Java int
(up to 2^31
or 2^32
), and the product of two such digits fits nicely in a Java long
.
当您认为十进制转换是最复杂的部分时,让我们保持基于十进制的模式。为了提高效率,我们将存储不是真实的小数位数,但在基地工作1 000 000 000 = 10 ^ 9 < 2 ^ 30。这符合在Java int(2 ^ 31或2 ^ 32),和两个数字的乘积在Java长很合适。
final static int BASE = 1000000000;
final static int BASE_DECIMAL_DIGITS = 9;
Then the digits-array:
然后digits-array:
private int[] digits;
Do we store the digits in little- or big endian, i.e. the bigger parts first or last? It does not really matter, so we decide on big-endian since this is how humans want to read it. (For now we concentrate on non-negative values - later we'll add a sign bit for negative numbers.)
我们是将数字存储在小的或大的endian中,也就是先存储大的部分还是最后存储大的部分?这并不重要,所以我们决定使用big-endian,因为这是人类想要的解读方式。(现在我们关注的是非负值——稍后我们将为负数添加一个符号位。)
For testing purposes, we add a constructor which allows initializing from such a int[].
出于测试的目的,我们添加了一个构造函数,该构造函数允许初始化这样的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();
}
As a added bonus, this constructor is also usable for a single int
(if smaller than BASE
), and even for no int
(which we'll interpret as 0). So, we now can do this:
作为额外的好处,这个构造函数也可以用于单个int(如果小于BASE),甚至不用于int(我们将把它解释为0)。
DecimalBigInt d = new DecimalBigInt(7, 5, 2, 12345);
System.out.println(d);
This gives us de.fencing_game.paul.examples.DecimalBigInt@6af62373
, not so useful. So, we add a toString()
method:
这给了我们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);
}
The output is now Big[7, 5, 2, 12345]
, which is more useful for testing, isn't it?
现在输出很大[7,5,2,12345],这对测试更有用,不是吗?
Second, conversion from decimal format.
We are lucky here: our base (10^9) is a power of the base we want to convert from (10). Thus, we always have the same number (9) of decimal digits representing one "our format" digit. (Of course, in the beginning there may be some digits less.) In the following code, decimal
is a String of decimal digits.
我们很幸运:基地(10 ^ 9)是一个基础的力量我们想把从(10)。因此,我们总是有相同的十进制数字(9)表示一个“我们的格式”数字。(当然,一开始可能会少一些数字。)在下面的代码中,decimal是一串十进制数字。
int decLen = decimal.length();
int bigLen = (decLen-1) / BASE_DECIMAL_DIGITS + 1;
This strange formula is a Java int way of writing bigLen = ceil(decLen/BASE_DECIMAL_DIGITS)
. (I hope it is correct, we'll later test it.)
这个奇怪的公式是编写bigLen = ceil的Java int方式(decLen/ base_decimal_位数)。(我希望它是正确的,我们稍后会测试它。)
int firstSome = decLen - (bigLen-1) * BASE_DECIMAL_DIGITS;
This is the length of the first block of decimal digits, should be between 1 and 9 (inclusive).
这是第一个十进制数字块的长度,应该在1到9之间(包括)。
We create our array:
我们创建数组:
int[] digits = new int[bigLen];
Looping through the digits to be created:
通过要创建的数字循环:
for(int i = 0; i < bigLen ; i++) {
Each of our digits is represented by a block of digits in the original number:
我们的每个数字都由原始数字中的一组数字表示:
String block =
decimal.substring(Math.max(firstSome + (i-1)*BASE_DECIMAL_DIGITS, 0),
firstSome + i *BASE_DECIMAL_DIGITS);
(The Math.max
is needed here for the first shorter block.) We now use the usual Integer parsing function, and put the result into the array:
(数学。第一个较短的区块需要max。现在我们使用通常的整数解析函数,并将结果放入数组中:
digits[i] = Integer.parseInt(block);
}
From the array now created we create our DecimalBigInt object:
从现在创建的数组中,我们创建了我们的DecimalBigInt对象:
return new DecimalBigInt(digits);
Let's see if this works:
让我们看看这是否可行:
DecimalBigInt d2 = DecimalBigInt.valueOf("12345678901234567890");
System.out.println(d2);
Output:
输出:
Big[12, 345678901, 234567890]
Looks right :-) We should test it with some other numbers (of different length) too.
看对了:-)我们也应该用别的数字(不同的长度)来测试一下。
Next part will be decimal formatting, this should be even easier.
下一部分将是十进制格式,这应该更简单。
Third, conversion to decimal format.
We need to output our individual digits as 9 decimal digits each. For this we can use the Formatter
class, which supports printf-like format strings.
我们需要将每个数字输出为9位小数。为此,我们可以使用Formatter类,它支持类似于printf的格式字符串。
A simple variant would be this:
一个简单的变体是:
public String toDecimalString() {
Formatter f = new Formatter();
for(int digit : digits) {
f.format("%09d", digit);
}
return f.toString();
}
This returns 000000007000000005000000002000012345
and 000000012345678901234567890
for our two numbers. This works for a round-trip (i.e. feeding it to the valueOf
method gives an equivalent object), but the leading zeros are not really nice to look at (and could create confusion with octal numbers). So we need to break apart our beautiful for-each loop and use a different formatting string for the first and the following digits.
这两个数字返回000000000000700000000500000000002000012345和0000000123456901234567890。这适用于往返(例如,向valueOf方法提供一个等价对象),但是前导0不是很好看(并且可能会与八进制数混淆)。因此,我们需要将我们漂亮的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();
}
Addition.
Let's start with addition, as this is simple (and we can use parts of it for the multiplication later).
让我们从加法开始,因为这很简单(我们可以在后面的乘法中使用它的一部分)。
/**
* calculates the sum of this and that.
*/
public DecimalBigInt plus(DecimalBigInt that) {
...
}
I want method names that you can read like you would read the formula, thus plus
, minus
, times
instead of add
, subtract
, multiply
.
我想要你能读的方法名,就像你读公式一样,所以加上,减去,乘以,而不是加,减,乘。
So, how does addition work? It works the same as we learned it in school for decimal numbers higher than 9: add the corresponding digits, and if for some of then the result is bigger than 10 (or BASE
in our case), carry one to the next digit. This can cause the resulting number to have one digit more than the original ones.
那么,加法是如何工作的呢?它的工作原理与我们在学校学到的小数点后9位数字相同:将相应的数字相加,如果其中一些数字的结果大于10(在我们的例子中是基数),将一个数字带入下一个数字。这会导致结果数字比原始数字多一个数字。
First we look at the simple case that both numbers have same number of digits. Then it looks simply like this:
首先,我们看一下两个数字都有相同数字的简单情况。然后它看起来就像这样:
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);
(We go from right to left, so we can carry any overflows to the next digit. This would be a bit prettier if we had decided using Little Endian format.)
(我们从右到左,这样我们就可以把任何超流传输到下一个数字。如果我们决定使用小的Endian格式,这将会更漂亮一点。
If both numbers do not have the same number of digits, it gets a bit more complicated.
如果两个数字都没有相同的数字,就会变得有点复杂。
To let it as simple as possible, we split it to several methods:
为了使它尽可能简单,我们将它分为几种方法:
This method adds one digit to an element in the array (which may already contain some non-zero value), and stores the result back in the array. If there was overflow, we carry it to the next digit (which has index one less, not one more) by means of a recursive call. This way we make sure our digits stay always in the valid range.
此方法向数组中的一个元素(该元素可能已经包含一些非零值)添加一个数字,并将结果存储回数组中。如果有溢出,我们通过递归调用将它传递到下一个数字(索引少了一个,而不是多了一个)。这样我们可以确保我们的数字始终保持在有效范围内。
/**
* 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);
}
}
The next does the same for a whole array of digits to add:
下一个则对要添加的整个数字数组执行相同的操作:
/**
* 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--;
}
}
Now we can implement our plus
method:
现在我们可以实现我们的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);
}
We could do a bit better here if we would look before if overflow is at all possible and only then create the array one bigger than necessary.
我们可以在这里做得更好,如果我们之前看到溢出是完全可能的,然后创建一个更大的数组。
Ah, one test: d2.plus(d2)
gives Big[24, 691357802, 469135780]
, which looks right.
一项测试:d2.plus(d2)给出了Big[24, 691357802, 469135780],看起来是对的。
Multiplication.
Let's remember back to school, how did we multiply bigger numbers on paper?
让我们回到学校,我们怎么把更大的数字写在纸上?
123 * 123
----------
369 <== 123 * 3
246 <== 123 * 2
123 <== 123 * 1
--------
15129
So, we have to multiply each digit[i] of the first number with each digit[j] of the second number, and add the product in digit[i+j] of the result (and pay attention to carry). Of course, here the indexes are counted from right, not from left. (Now i really wish I had used little-endian numbers.)
因此,我们必须将第一个数字的每一个数字[i]与第二个数字的每一个数字[j]相乘,然后将结果的数字[i+j]相加(注意进位)。当然,这里的索引是从右开始计数,而不是从左开始。(现在我真希望我用的是little-endian的数字。)
Since the product of two of our digits can get outside of the range of int
, we use long
for multiplication.
由于两个数字的乘积可以超出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);
}
Now we can see why I declared my addDigits
method to take a resultIndex
parameter. (And I just changed the last argument to a varargs parameter, to be able to write this here better.)
现在我们可以看到为什么我声明了addnumbers方法以获取resultIndex参数。(我把最后一个参数改成了varargs参数,这样可以更好地写在这里)
So, here the cross-multiplying method:
交叉相乘的方法是
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]);
}
}
}
I hope I have the index-calculations right. With a little-endian representation, it would have been multiplyDigit(result, resultIndex + i + j, leftFactor[i], rightFactor[j])
- quite clearer, isn't it?
我希望指数计算正确。如果使用小端表示,它应该是多位数(result, resultIndex + i + j, leftFactor[i], rightFactor[j])——更清楚,不是吗?
Our times
method now has only to allocate the result array, invoke multiplyDigits
and wrap the result.
我们的times方法现在只需分配结果数组、调用多位数并包装结果。
/**
* 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);
}
For testing, d2.times(d2)
gives Big[152, 415787532, 388367501, 905199875, 19052100]
, which is the same what my Emacs calc calculates here.
对于测试,d2.times(d2)给出了Big[152, 415787532, 388367501, 905199875, 19052100],这和我的Emacs calc计算的是一样的。
Comparison
We want to be able to compare two of our objects. So, we implement Comparable<DecimalBigInt>
and its compareTo method.
我们想要比较两个物体。因此,我们实现了可比的
public int compareTo(DecimalBigInt that) {
How to know if one of our numbers is bigger than another? First, we compare the length of the arrays. As we took care not to induce any leading zeros (did we?), the longer array should have the bigger number.
如何知道一个数是否大于另一个数?首先,我们比较数组的长度。由于我们注意不引入任何前导零(不是吗?),长数组应该有更大的数。
if(this.digits.length < that.digits.length) {
return -1;
}
if (that.digits.length < this.digits.length) {
return 1;
}
If the length are same, we can compare elementwise. Since we use big endian (i.e. the big end comes first), we start at the beginning.
如果长度相同,我们可以用元素来比较。因为我们使用了大的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;
}
}
If everything was same, obviously our numbers are identical, and we can return 0
.
如果所有东西都是一样的,显然我们的数是一样的,我们可以返回0。
return 0;
}
equals
+ hashCode()
Every good immutable class should implement equals()
and hashCode()
in a suitable (and compatible) way.
每个好的不可变类都应该以合适的(和兼容的)方式实现equals()和hashCode()。
For our hashCode()
, we simply sum up the digits, multiplying them with a small prime to make sure digit-switching does not result in same hash code:
对于我们的hashCode(),我们只对数字进行求和,并将它们与一个小素数相乘,以确保digit交换不会导致相同的哈希代码:
/**
* calculates a hashCode for this object.
*/
public int hashCode() {
int hash = 0;
for(int digit : digits) {
hash = hash * 13 + digit;
}
return hash;
}
In the equals()
method we simply can delegate to the compareTo method, instead of implementing the same algorithm again:
在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;
}
So, enough for today. Subtraction (and maybe negative numbers) and division are more complicated, so I'm omitting them for now. For calculating the factorial of 90 this should be enough.
因此,足够的今天。减法(也许是负数)和除法更复杂,所以我暂时把它们省略掉。要计算90的阶乘,这个就足够了。
Calculating big factorials:
Here the factorial function:
这里的阶乘函数:
/**
* 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;
}
This gives us
这给了我们
fac(90) = 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000
Converting from arbitrary-radix representations
Prompted by the next question of frodosamoa, I wrote my answer about how to convert from arbitrary (positional) number systems in the one in which we can (or want to) calculate. (In the example there, I converted from trinary to decimal, while the question was about decimal to binary.)
受弗洛多萨摩亚下一个问题的启发,我写下了我的答案,关于如何从任意(位置)数字系统转换到我们可以(或想要)计算的那个系统。(在这个例子中,我从三进制转换为十进制,而问题是十进制转换为二进制。)
Here we want to convert from an arbitrary number system (okay, with radix between 2 and 36, so we can use Character.digit()
to convert single digits to ints) to our system with radix BASE
(= 1.000.000.000, but this is not really important here).
在这里,我们想要从任意的数字系统(好的,基数为2到36,因此我们可以使用Character.digit()将单个数字转换为int)转换到基数为(= 1.000.000.000)的系统中,但这在这里并不重要。
Basically we use Horner scheme to calculate the value of polynomial with the digits as coefficients at the point given by the radix.
基本上我们用霍纳方案来计算多项式的值,用数字作为基数给定点的系数。
sum[i=0..n] digit[i] * radix^i
can be calculated with this loop:
可通过此循环计算:
value = 0;
for i = n .. 0
value = value * radix + digit[i]
return value
Since our input strings are big-endian, we don't have to count down, but can use a simple enhanced for loop. (It looks more ugly in Java, since we have no operator overloading, and no autoboxing from int to our DecimalBigInt type.)
由于我们的输入字符串是大端的,所以我们不需要倒数,但是可以使用一个简单的增强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;
}
In my actual implementation I added some error checking (and exception throwing) to ensure that we really have a valid number, and of course a documentation comment.
在我的实际实现中,我添加了一些错误检查(和异常抛出),以确保我们确实有一个有效的数字,当然还有文档注释。
Converting to an arbitrary positional system is more complicated, as it involves remainder and division (by the arbitrary radix), which we did not implement yet - so not for now. It will be done when I have a good idea on how to do division. (We need only division by small (one-digit) numbers here, which may be easier than a general division.)
转换到任意位置系统要复杂得多,因为它涉及余数和除法(用任意的基数),我们还没有实现这一点——所以现在还没有实现。当我对如何做除法有了一个好主意时,它就会完成。(这里我们只需要按小(一位数)的数字除法,这可能比一般除法简单。)
Division by small numbers
In school, I learned long division. Here is an example for a small (one-digit) divisor, in the notation we use here in Germany (with annotations about the background calculations, which we normally would not write), in decimal system:
在学校,我学了长除法。这里有一个小(一位数)除数的例子,用我们在德国使用的表示法(加上我们通常不会写的关于背景计算的注释),用十进制表示:
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.
Of couse, we don't need to calculate these products (0, 12, 0, 30, 42) and subtract them if we have a native remainder operation. Then it looks like this (of course, we here would not need to write the operations):
在couse中,我们不需要计算这些产品(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.
This already looks quite like short division, if we write it in another format.
如果我们用另一种格式来写的话,这看起来已经很像短除法了。
We can observe (and prove) the following:
我们可以观察(并证明)如下:
If we have a two-digit number x with first digit smaller than our divisor d, than x / d
is a one-digit number, and x % d
is also a one-digit number, smaller than d. This, together with induction, shows that we only ever need to divide (with remainder) two-digit numbers by our divisor.
如果我们有一个两位数x与第一位数小于除数d,比x / d是一个一位数,和x % d也是一个一位数,比d。这个小,加上感应,表明我们只需要(剩余)两位数除以除数。
Coming back to our big numbers with radix BASE: all two-digit numbers are representable as a Java long
, and there we have native /
and %
.
回到基数为基数的大数:所有两位数的数字都可以表示为Java长,这里我们有本机/和%。
/**
* 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;
}
We will now call this method in a loop, always feeding the result from the previous call back as lastRemainder
.
我们现在将在一个循环中调用这个方法,它总是将先前调用的结果作为lastresistantwith值返回。
/**
* 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;
}
This method still returns an int, the remainder.
这个方法仍然返回一个整数,其余的。
Now we want to have a public method returning a DecimalBigInt, so we create one. It has the task to check the arguments, create an array for the working method, discard the remainder, and create a DecimalBigInt from the result. (The constructor removes a leading zero which may be there.)
现在我们想要一个返回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);
}
We also have a similar method, which returns the remainder instead:
我们还有一个类似的方法,它返回的是余数:
/**
* 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);
}
These methods can be invoked like this:
可以这样调用这些方法:
DecimalBigInt d3_by_100 = d3.divideBy(100);
System.out.println("d3/100 = " + d3_by_100);
System.out.println("d3%100 = " + d3.modulo(100));
Conversion to arbitrary radix
Now we have the basics to convert to an arbitrary radix. Of course, not really arbitrary, only radixes smaller than BASE
are allowed, but this should not be a too big problem.
现在我们已经有了转换到任意基数的基础。当然,并不是任意的,只允许比基数小的基数,但这不应该是一个太大的问题。
As already answered in another answer about converting numbers, we have to do "division, remainder, multiply, add. The "multiply-add" part is in fact only putting together the individual digits, so we can replace it by a simple array-access.
正如在另一个关于数字转换的回答中已经回答的那样,我们必须做“除法、余数、乘、加”。“multiply-add”部分实际上只是把单个数字放在一起,所以我们可以用一个简单的数组访问来替换它。
As we always need both the quotient and the remainder, we won't use the public methods modulo
and divideBy
, but instead repeatedly call the divideDigits
method.
因为我们总是需要商和余数,所以我们不会使用公共方法modulo和divideBy,而是反复调用dividenumbers方法。
/**
* 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!");
}
First, a special-case handling for 0.
首先,对0进行特殊情况处理。
// zero has no digits.
if(digits.length == 0)
return new int[0];
Then, we create an array for the result digits (long enough), and some other 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
is the number of digits (excluding leading zeroes) in the last quotient. If this is 0, we are done.
quotLen是最后一个商中位数(不包括前导零)的个数。如果这是0,我们做完了。
while(quotLen > 0) {
A new array for the next quotient.
下一个商的新数组。
int[] quot = new int[quotLen];
The quotient-and-remainder operation. The quotient is now in quot
, the remainder in rem
.
quotient-and-remainder操作。商现在在rem中,余数在rem中。
int rem = divideDigits(quot, 0,
current, current.length - quotLen,
radix);
We put the remainder in the output array (filling it from the last digit).
我们将余数放入输出数组(从最后一位填充)。
rDigits[rIndex] = rem;
rIndex --;
Then we swap the arrays for the next round.
然后我们将数组交换到下一轮。
current = quot;
If there are leading zeros in the quotient (there will be at most one, since radix is smaller than BASE), we shrink the quotient size by one. The next array will be smaller.
如果商中有前导0(由于基数小于底,最多也只有1),我们就把商的大小缩小1。下一个数组会更小。
if(current[0] == 0) {
// omit leading zeros in next round.
quotLen--;
}
}
After the loop there may be leading zeros in the rDigits array, and we cut them off.
在循环之后,r手指数组中可能有前导0,我们将它们切断。
// cut of leading zeros in rDigits:
while(rIndex < 0 || rDigits[rIndex] == 0) {
rIndex++;
}
return Arrays.copyOfRange(rDigits, rIndex, rDigits.length);
}
That's it. It looks a bit complicated, though. Here is an example of how to use it:
就是这样。不过看起来有点复杂。这里有一个如何使用它的例子:
System.out.println("d4 in base 11: " +
Arrays.toString(d4.convertTo(11)));
System.out.println("d5 in base 7: " +
Arrays.toString(d5.convertTo(7)));
These print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]
and [1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0]
, just the same numbers as we parsed before (from a String, though).
这些打印(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],相同的数字我们解析之前(从一个字符串,虽然)。
Based on this we can also format as a 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();
}
#2
2
You might want to implement or research a library for binary-coded decimal if you're trying to avoid BigInteger
. You can accomplish factorial of 90 with BigInteger
if you want to use it though:
如果您想避免BigInteger,您可能想要实现或研究一个二进制编码的十进制的库。如果你想使用它,你可以用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;
}
#3
1
Arithmetic operations in Java using the operators +
, -
, *
, /
, and %
are bound by the constraints of the Java primitive data types.
Java中使用操作符+、-、*、/和%的算术操作受到Java基本数据类型的约束。
This means that if you can't fit your desired numbers into the range of, say a double
or long
then you'll have to use a "big number" library, such as the one built-in to Java (BigDecimal, BigInteger), or a third-party library, or write your own. This also means that you cannot use the arithmetic operators since Java does not support operator overloading.
这意味着,如果您无法将所需的数字放入双精度或长精度的范围内,那么您将不得不使用“big number”库,例如Java内置的库(BigDecimal, BigInteger)或第三方库,或者编写您自己的库。这也意味着您不能使用算术运算符,因为Java不支持操作符重载。
#4
1
Use the code below to multiply numbers of any length:-
使用下面的代码乘以任何长度的数字:-
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));
}
}
#5
0
strong text public class BigInteger {
强文本公共类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));
}
}
}
#6
0
When I want to do 90! or some other massive calculation, I try and use an int[] array, each element holding one of the digits. Then I apply the traditional multiplication we using pen and paper to get the answer in another int[] array.
当我想做90!或者其他大量的计算,我尝试使用int[]数组,每个元素都包含一个数字。然后我应用传统的乘法,我们使用钢笔和纸来得到另一个int[]数组的答案。
This is the code I wrote in Java which calculates 100! rather quickly. Feel free to use this however you like.
这是我在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;
}
#7
0
If we have really big numbers on which we want to perform arithmetic operations than they must be in some object form such as Strings.
如果我们有很大的数字我们想要进行算术运算它们必须是某种对象形式,比如字符串。
Let their be strings with the character length greater than the range of BigInteger.
让它们的字符串的字符长度大于BigInteger的范围。
In this case I'll perform arithmetic operation the way we do it on a notebook. For Example - Let's assume we have to do the addition. Start with comparing the two strings for length. Make three new Strings. The First String is the smaller one. The Second String is the rightmost substring of the longer string with length equal to the smaller string. The third string is the leftover long string from the left side. Now add the first and second string from the end converting characters to integers, one character at a time and keeping the carry in an int variable. Immediately after each addition, append the sum in a StringBuffer. After the two strings are added, do the same operation for the third string and keep on adding the carry. In the end reverse the StringBuffer and return the String.
在这种情况下,我将像在笔记本上那样执行算术运算。例如,假设我们必须做加法。从比较两个字符串的长度开始。让三个新的字符串。第一个弦是小的。第二个字符串是较长的字符串的最右边的子字符串,长度等于较小的字符串。第三个字符串是左边剩下的长字符串。现在,在将字符转换为整数的末尾添加第一个和第二个字符串,每次一个字符,并将进位保存在int变量中。在每次添加之后,立即在StringBuffer中追加和。在添加两个字符串之后,对第三个字符串执行相同的操作,并继续添加进位。最后,反转StringBuffer并返回字符串。
Here is the code I used for Addition
这是我用来添加的代码。
public String addNumber(String input1,String input2){
int n=0;String tempStr;
String one="";
String two="";
if(input1.length()>input2.length()){
n=input1.length()-input2.length();
tempStr=new String(input1);
one=new String(input1.substring(n,input1.length()));
two=new String(input2);
}else{
n=input2.length()-input1.length();
tempStr=new String(input2);
one=new String(input2.substring(n,input2.length()));
two=new String(input1);
}
StringBuffer temp=new StringBuffer();
for(int i=0;i<n;i++){
temp.append(tempStr.charAt(i));
}
StringBuffer newBuf=new StringBuffer();
int carry=0;
int c;
for(int i=one.length()-1;i>=0;i--){
int a=Character.getNumericValue(one.charAt(i));
int b=Character.getNumericValue(two.charAt(i));
c=a+b+carry;
newBuf.append(""+(c%10));
c=c/10;
carry=c%10;
}
String news=new String(temp);
for(int i=news.length()-1;i>=0;i--){
c=(Character.getNumericValue(news.charAt(i)))+carry;
newBuf.append(""+(c%10));
c=c/10;
carry=c%10;
}
if(carry==1){
newBuf.append(""+carry);
}
String newisis=new String(newBuf.reverse());
return newisis;
}
#1
237
I think a programmer should have implemented his own bignum-library once, so welcome here.
我认为一个程序员应该实现他自己的bignum-library一次,所以欢迎来到这里。
(Of course, later you'll get that BigInteger is better, and use this, but it is a valuable learning experience.)
(当然,稍后您会得到BigInteger是更好的,并使用它,但这是一种宝贵的学习经验。)
(You can follow the source code of this course life on github. Also, I remade this (a bit polished) into a 14-part blog series.)
(你可以参考github上本课程的源代码。此外,我还把这个(稍微润饰一下)重做了一个包含14个部分的博客系列。
Creating a simple Big number class in Java
So, what do we need?
那么,我们需要什么呢?
First, a representation of the number,
based on the datatypes which Java gives us.
基于Java提供的数据类型。
As you think the decimal conversion is the most complicated part, let's stay in a decimal based mode. For efficiency, we'll store not real decimal digits, but work in base 1 000 000 000 = 10^9 < 2^30
. This fits in a Java int
(up to 2^31
or 2^32
), and the product of two such digits fits nicely in a Java long
.
当您认为十进制转换是最复杂的部分时,让我们保持基于十进制的模式。为了提高效率,我们将存储不是真实的小数位数,但在基地工作1 000 000 000 = 10 ^ 9 < 2 ^ 30。这符合在Java int(2 ^ 31或2 ^ 32),和两个数字的乘积在Java长很合适。
final static int BASE = 1000000000;
final static int BASE_DECIMAL_DIGITS = 9;
Then the digits-array:
然后digits-array:
private int[] digits;
Do we store the digits in little- or big endian, i.e. the bigger parts first or last? It does not really matter, so we decide on big-endian since this is how humans want to read it. (For now we concentrate on non-negative values - later we'll add a sign bit for negative numbers.)
我们是将数字存储在小的或大的endian中,也就是先存储大的部分还是最后存储大的部分?这并不重要,所以我们决定使用big-endian,因为这是人类想要的解读方式。(现在我们关注的是非负值——稍后我们将为负数添加一个符号位。)
For testing purposes, we add a constructor which allows initializing from such a int[].
出于测试的目的,我们添加了一个构造函数,该构造函数允许初始化这样的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();
}
As a added bonus, this constructor is also usable for a single int
(if smaller than BASE
), and even for no int
(which we'll interpret as 0). So, we now can do this:
作为额外的好处,这个构造函数也可以用于单个int(如果小于BASE),甚至不用于int(我们将把它解释为0)。
DecimalBigInt d = new DecimalBigInt(7, 5, 2, 12345);
System.out.println(d);
This gives us de.fencing_game.paul.examples.DecimalBigInt@6af62373
, not so useful. So, we add a toString()
method:
这给了我们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);
}
The output is now Big[7, 5, 2, 12345]
, which is more useful for testing, isn't it?
现在输出很大[7,5,2,12345],这对测试更有用,不是吗?
Second, conversion from decimal format.
We are lucky here: our base (10^9) is a power of the base we want to convert from (10). Thus, we always have the same number (9) of decimal digits representing one "our format" digit. (Of course, in the beginning there may be some digits less.) In the following code, decimal
is a String of decimal digits.
我们很幸运:基地(10 ^ 9)是一个基础的力量我们想把从(10)。因此,我们总是有相同的十进制数字(9)表示一个“我们的格式”数字。(当然,一开始可能会少一些数字。)在下面的代码中,decimal是一串十进制数字。
int decLen = decimal.length();
int bigLen = (decLen-1) / BASE_DECIMAL_DIGITS + 1;
This strange formula is a Java int way of writing bigLen = ceil(decLen/BASE_DECIMAL_DIGITS)
. (I hope it is correct, we'll later test it.)
这个奇怪的公式是编写bigLen = ceil的Java int方式(decLen/ base_decimal_位数)。(我希望它是正确的,我们稍后会测试它。)
int firstSome = decLen - (bigLen-1) * BASE_DECIMAL_DIGITS;
This is the length of the first block of decimal digits, should be between 1 and 9 (inclusive).
这是第一个十进制数字块的长度,应该在1到9之间(包括)。
We create our array:
我们创建数组:
int[] digits = new int[bigLen];
Looping through the digits to be created:
通过要创建的数字循环:
for(int i = 0; i < bigLen ; i++) {
Each of our digits is represented by a block of digits in the original number:
我们的每个数字都由原始数字中的一组数字表示:
String block =
decimal.substring(Math.max(firstSome + (i-1)*BASE_DECIMAL_DIGITS, 0),
firstSome + i *BASE_DECIMAL_DIGITS);
(The Math.max
is needed here for the first shorter block.) We now use the usual Integer parsing function, and put the result into the array:
(数学。第一个较短的区块需要max。现在我们使用通常的整数解析函数,并将结果放入数组中:
digits[i] = Integer.parseInt(block);
}
From the array now created we create our DecimalBigInt object:
从现在创建的数组中,我们创建了我们的DecimalBigInt对象:
return new DecimalBigInt(digits);
Let's see if this works:
让我们看看这是否可行:
DecimalBigInt d2 = DecimalBigInt.valueOf("12345678901234567890");
System.out.println(d2);
Output:
输出:
Big[12, 345678901, 234567890]
Looks right :-) We should test it with some other numbers (of different length) too.
看对了:-)我们也应该用别的数字(不同的长度)来测试一下。
Next part will be decimal formatting, this should be even easier.
下一部分将是十进制格式,这应该更简单。
Third, conversion to decimal format.
We need to output our individual digits as 9 decimal digits each. For this we can use the Formatter
class, which supports printf-like format strings.
我们需要将每个数字输出为9位小数。为此,我们可以使用Formatter类,它支持类似于printf的格式字符串。
A simple variant would be this:
一个简单的变体是:
public String toDecimalString() {
Formatter f = new Formatter();
for(int digit : digits) {
f.format("%09d", digit);
}
return f.toString();
}
This returns 000000007000000005000000002000012345
and 000000012345678901234567890
for our two numbers. This works for a round-trip (i.e. feeding it to the valueOf
method gives an equivalent object), but the leading zeros are not really nice to look at (and could create confusion with octal numbers). So we need to break apart our beautiful for-each loop and use a different formatting string for the first and the following digits.
这两个数字返回000000000000700000000500000000002000012345和0000000123456901234567890。这适用于往返(例如,向valueOf方法提供一个等价对象),但是前导0不是很好看(并且可能会与八进制数混淆)。因此,我们需要将我们漂亮的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();
}
Addition.
Let's start with addition, as this is simple (and we can use parts of it for the multiplication later).
让我们从加法开始,因为这很简单(我们可以在后面的乘法中使用它的一部分)。
/**
* calculates the sum of this and that.
*/
public DecimalBigInt plus(DecimalBigInt that) {
...
}
I want method names that you can read like you would read the formula, thus plus
, minus
, times
instead of add
, subtract
, multiply
.
我想要你能读的方法名,就像你读公式一样,所以加上,减去,乘以,而不是加,减,乘。
So, how does addition work? It works the same as we learned it in school for decimal numbers higher than 9: add the corresponding digits, and if for some of then the result is bigger than 10 (or BASE
in our case), carry one to the next digit. This can cause the resulting number to have one digit more than the original ones.
那么,加法是如何工作的呢?它的工作原理与我们在学校学到的小数点后9位数字相同:将相应的数字相加,如果其中一些数字的结果大于10(在我们的例子中是基数),将一个数字带入下一个数字。这会导致结果数字比原始数字多一个数字。
First we look at the simple case that both numbers have same number of digits. Then it looks simply like this:
首先,我们看一下两个数字都有相同数字的简单情况。然后它看起来就像这样:
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);
(We go from right to left, so we can carry any overflows to the next digit. This would be a bit prettier if we had decided using Little Endian format.)
(我们从右到左,这样我们就可以把任何超流传输到下一个数字。如果我们决定使用小的Endian格式,这将会更漂亮一点。
If both numbers do not have the same number of digits, it gets a bit more complicated.
如果两个数字都没有相同的数字,就会变得有点复杂。
To let it as simple as possible, we split it to several methods:
为了使它尽可能简单,我们将它分为几种方法:
This method adds one digit to an element in the array (which may already contain some non-zero value), and stores the result back in the array. If there was overflow, we carry it to the next digit (which has index one less, not one more) by means of a recursive call. This way we make sure our digits stay always in the valid range.
此方法向数组中的一个元素(该元素可能已经包含一些非零值)添加一个数字,并将结果存储回数组中。如果有溢出,我们通过递归调用将它传递到下一个数字(索引少了一个,而不是多了一个)。这样我们可以确保我们的数字始终保持在有效范围内。
/**
* 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);
}
}
The next does the same for a whole array of digits to add:
下一个则对要添加的整个数字数组执行相同的操作:
/**
* 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--;
}
}
Now we can implement our plus
method:
现在我们可以实现我们的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);
}
We could do a bit better here if we would look before if overflow is at all possible and only then create the array one bigger than necessary.
我们可以在这里做得更好,如果我们之前看到溢出是完全可能的,然后创建一个更大的数组。
Ah, one test: d2.plus(d2)
gives Big[24, 691357802, 469135780]
, which looks right.
一项测试:d2.plus(d2)给出了Big[24, 691357802, 469135780],看起来是对的。
Multiplication.
Let's remember back to school, how did we multiply bigger numbers on paper?
让我们回到学校,我们怎么把更大的数字写在纸上?
123 * 123
----------
369 <== 123 * 3
246 <== 123 * 2
123 <== 123 * 1
--------
15129
So, we have to multiply each digit[i] of the first number with each digit[j] of the second number, and add the product in digit[i+j] of the result (and pay attention to carry). Of course, here the indexes are counted from right, not from left. (Now i really wish I had used little-endian numbers.)
因此,我们必须将第一个数字的每一个数字[i]与第二个数字的每一个数字[j]相乘,然后将结果的数字[i+j]相加(注意进位)。当然,这里的索引是从右开始计数,而不是从左开始。(现在我真希望我用的是little-endian的数字。)
Since the product of two of our digits can get outside of the range of int
, we use long
for multiplication.
由于两个数字的乘积可以超出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);
}
Now we can see why I declared my addDigits
method to take a resultIndex
parameter. (And I just changed the last argument to a varargs parameter, to be able to write this here better.)
现在我们可以看到为什么我声明了addnumbers方法以获取resultIndex参数。(我把最后一个参数改成了varargs参数,这样可以更好地写在这里)
So, here the cross-multiplying method:
交叉相乘的方法是
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]);
}
}
}
I hope I have the index-calculations right. With a little-endian representation, it would have been multiplyDigit(result, resultIndex + i + j, leftFactor[i], rightFactor[j])
- quite clearer, isn't it?
我希望指数计算正确。如果使用小端表示,它应该是多位数(result, resultIndex + i + j, leftFactor[i], rightFactor[j])——更清楚,不是吗?
Our times
method now has only to allocate the result array, invoke multiplyDigits
and wrap the result.
我们的times方法现在只需分配结果数组、调用多位数并包装结果。
/**
* 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);
}
For testing, d2.times(d2)
gives Big[152, 415787532, 388367501, 905199875, 19052100]
, which is the same what my Emacs calc calculates here.
对于测试,d2.times(d2)给出了Big[152, 415787532, 388367501, 905199875, 19052100],这和我的Emacs calc计算的是一样的。
Comparison
We want to be able to compare two of our objects. So, we implement Comparable<DecimalBigInt>
and its compareTo method.
我们想要比较两个物体。因此,我们实现了可比的
public int compareTo(DecimalBigInt that) {
How to know if one of our numbers is bigger than another? First, we compare the length of the arrays. As we took care not to induce any leading zeros (did we?), the longer array should have the bigger number.
如何知道一个数是否大于另一个数?首先,我们比较数组的长度。由于我们注意不引入任何前导零(不是吗?),长数组应该有更大的数。
if(this.digits.length < that.digits.length) {
return -1;
}
if (that.digits.length < this.digits.length) {
return 1;
}
If the length are same, we can compare elementwise. Since we use big endian (i.e. the big end comes first), we start at the beginning.
如果长度相同,我们可以用元素来比较。因为我们使用了大的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;
}
}
If everything was same, obviously our numbers are identical, and we can return 0
.
如果所有东西都是一样的,显然我们的数是一样的,我们可以返回0。
return 0;
}
equals
+ hashCode()
Every good immutable class should implement equals()
and hashCode()
in a suitable (and compatible) way.
每个好的不可变类都应该以合适的(和兼容的)方式实现equals()和hashCode()。
For our hashCode()
, we simply sum up the digits, multiplying them with a small prime to make sure digit-switching does not result in same hash code:
对于我们的hashCode(),我们只对数字进行求和,并将它们与一个小素数相乘,以确保digit交换不会导致相同的哈希代码:
/**
* calculates a hashCode for this object.
*/
public int hashCode() {
int hash = 0;
for(int digit : digits) {
hash = hash * 13 + digit;
}
return hash;
}
In the equals()
method we simply can delegate to the compareTo method, instead of implementing the same algorithm again:
在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;
}
So, enough for today. Subtraction (and maybe negative numbers) and division are more complicated, so I'm omitting them for now. For calculating the factorial of 90 this should be enough.
因此,足够的今天。减法(也许是负数)和除法更复杂,所以我暂时把它们省略掉。要计算90的阶乘,这个就足够了。
Calculating big factorials:
Here the factorial function:
这里的阶乘函数:
/**
* 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;
}
This gives us
这给了我们
fac(90) = 1485715964481761497309522733620825737885569961284688766942216863704985393094065876545992131370884059645617234469978112000000000000000000000
Converting from arbitrary-radix representations
Prompted by the next question of frodosamoa, I wrote my answer about how to convert from arbitrary (positional) number systems in the one in which we can (or want to) calculate. (In the example there, I converted from trinary to decimal, while the question was about decimal to binary.)
受弗洛多萨摩亚下一个问题的启发,我写下了我的答案,关于如何从任意(位置)数字系统转换到我们可以(或想要)计算的那个系统。(在这个例子中,我从三进制转换为十进制,而问题是十进制转换为二进制。)
Here we want to convert from an arbitrary number system (okay, with radix between 2 and 36, so we can use Character.digit()
to convert single digits to ints) to our system with radix BASE
(= 1.000.000.000, but this is not really important here).
在这里,我们想要从任意的数字系统(好的,基数为2到36,因此我们可以使用Character.digit()将单个数字转换为int)转换到基数为(= 1.000.000.000)的系统中,但这在这里并不重要。
Basically we use Horner scheme to calculate the value of polynomial with the digits as coefficients at the point given by the radix.
基本上我们用霍纳方案来计算多项式的值,用数字作为基数给定点的系数。
sum[i=0..n] digit[i] * radix^i
can be calculated with this loop:
可通过此循环计算:
value = 0;
for i = n .. 0
value = value * radix + digit[i]
return value
Since our input strings are big-endian, we don't have to count down, but can use a simple enhanced for loop. (It looks more ugly in Java, since we have no operator overloading, and no autoboxing from int to our DecimalBigInt type.)
由于我们的输入字符串是大端的,所以我们不需要倒数,但是可以使用一个简单的增强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;
}
In my actual implementation I added some error checking (and exception throwing) to ensure that we really have a valid number, and of course a documentation comment.
在我的实际实现中,我添加了一些错误检查(和异常抛出),以确保我们确实有一个有效的数字,当然还有文档注释。
Converting to an arbitrary positional system is more complicated, as it involves remainder and division (by the arbitrary radix), which we did not implement yet - so not for now. It will be done when I have a good idea on how to do division. (We need only division by small (one-digit) numbers here, which may be easier than a general division.)
转换到任意位置系统要复杂得多,因为它涉及余数和除法(用任意的基数),我们还没有实现这一点——所以现在还没有实现。当我对如何做除法有了一个好主意时,它就会完成。(这里我们只需要按小(一位数)的数字除法,这可能比一般除法简单。)
Division by small numbers
In school, I learned long division. Here is an example for a small (one-digit) divisor, in the notation we use here in Germany (with annotations about the background calculations, which we normally would not write), in decimal system:
在学校,我学了长除法。这里有一个小(一位数)除数的例子,用我们在德国使用的表示法(加上我们通常不会写的关于背景计算的注释),用十进制表示:
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.
Of couse, we don't need to calculate these products (0, 12, 0, 30, 42) and subtract them if we have a native remainder operation. Then it looks like this (of course, we here would not need to write the operations):
在couse中,我们不需要计算这些产品(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.
This already looks quite like short division, if we write it in another format.
如果我们用另一种格式来写的话,这看起来已经很像短除法了。
We can observe (and prove) the following:
我们可以观察(并证明)如下:
If we have a two-digit number x with first digit smaller than our divisor d, than x / d
is a one-digit number, and x % d
is also a one-digit number, smaller than d. This, together with induction, shows that we only ever need to divide (with remainder) two-digit numbers by our divisor.
如果我们有一个两位数x与第一位数小于除数d,比x / d是一个一位数,和x % d也是一个一位数,比d。这个小,加上感应,表明我们只需要(剩余)两位数除以除数。
Coming back to our big numbers with radix BASE: all two-digit numbers are representable as a Java long
, and there we have native /
and %
.
回到基数为基数的大数:所有两位数的数字都可以表示为Java长,这里我们有本机/和%。
/**
* 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;
}
We will now call this method in a loop, always feeding the result from the previous call back as lastRemainder
.
我们现在将在一个循环中调用这个方法,它总是将先前调用的结果作为lastresistantwith值返回。
/**
* 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;
}
This method still returns an int, the remainder.
这个方法仍然返回一个整数,其余的。
Now we want to have a public method returning a DecimalBigInt, so we create one. It has the task to check the arguments, create an array for the working method, discard the remainder, and create a DecimalBigInt from the result. (The constructor removes a leading zero which may be there.)
现在我们想要一个返回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);
}
We also have a similar method, which returns the remainder instead:
我们还有一个类似的方法,它返回的是余数:
/**
* 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);
}
These methods can be invoked like this:
可以这样调用这些方法:
DecimalBigInt d3_by_100 = d3.divideBy(100);
System.out.println("d3/100 = " + d3_by_100);
System.out.println("d3%100 = " + d3.modulo(100));
Conversion to arbitrary radix
Now we have the basics to convert to an arbitrary radix. Of course, not really arbitrary, only radixes smaller than BASE
are allowed, but this should not be a too big problem.
现在我们已经有了转换到任意基数的基础。当然,并不是任意的,只允许比基数小的基数,但这不应该是一个太大的问题。
As already answered in another answer about converting numbers, we have to do "division, remainder, multiply, add. The "multiply-add" part is in fact only putting together the individual digits, so we can replace it by a simple array-access.
正如在另一个关于数字转换的回答中已经回答的那样,我们必须做“除法、余数、乘、加”。“multiply-add”部分实际上只是把单个数字放在一起,所以我们可以用一个简单的数组访问来替换它。
As we always need both the quotient and the remainder, we won't use the public methods modulo
and divideBy
, but instead repeatedly call the divideDigits
method.
因为我们总是需要商和余数,所以我们不会使用公共方法modulo和divideBy,而是反复调用dividenumbers方法。
/**
* 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!");
}
First, a special-case handling for 0.
首先,对0进行特殊情况处理。
// zero has no digits.
if(digits.length == 0)
return new int[0];
Then, we create an array for the result digits (long enough), and some other 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
is the number of digits (excluding leading zeroes) in the last quotient. If this is 0, we are done.
quotLen是最后一个商中位数(不包括前导零)的个数。如果这是0,我们做完了。
while(quotLen > 0) {
A new array for the next quotient.
下一个商的新数组。
int[] quot = new int[quotLen];
The quotient-and-remainder operation. The quotient is now in quot
, the remainder in rem
.
quotient-and-remainder操作。商现在在rem中,余数在rem中。
int rem = divideDigits(quot, 0,
current, current.length - quotLen,
radix);
We put the remainder in the output array (filling it from the last digit).
我们将余数放入输出数组(从最后一位填充)。
rDigits[rIndex] = rem;
rIndex --;
Then we swap the arrays for the next round.
然后我们将数组交换到下一轮。
current = quot;
If there are leading zeros in the quotient (there will be at most one, since radix is smaller than BASE), we shrink the quotient size by one. The next array will be smaller.
如果商中有前导0(由于基数小于底,最多也只有1),我们就把商的大小缩小1。下一个数组会更小。
if(current[0] == 0) {
// omit leading zeros in next round.
quotLen--;
}
}
After the loop there may be leading zeros in the rDigits array, and we cut them off.
在循环之后,r手指数组中可能有前导0,我们将它们切断。
// cut of leading zeros in rDigits:
while(rIndex < 0 || rDigits[rIndex] == 0) {
rIndex++;
}
return Arrays.copyOfRange(rDigits, rIndex, rDigits.length);
}
That's it. It looks a bit complicated, though. Here is an example of how to use it:
就是这样。不过看起来有点复杂。这里有一个如何使用它的例子:
System.out.println("d4 in base 11: " +
Arrays.toString(d4.convertTo(11)));
System.out.println("d5 in base 7: " +
Arrays.toString(d5.convertTo(7)));
These print [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]
and [1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0]
, just the same numbers as we parsed before (from a String, though).
这些打印(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],相同的数字我们解析之前(从一个字符串,虽然)。
Based on this we can also format as a 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();
}
#2
2
You might want to implement or research a library for binary-coded decimal if you're trying to avoid BigInteger
. You can accomplish factorial of 90 with BigInteger
if you want to use it though:
如果您想避免BigInteger,您可能想要实现或研究一个二进制编码的十进制的库。如果你想使用它,你可以用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;
}
#3
1
Arithmetic operations in Java using the operators +
, -
, *
, /
, and %
are bound by the constraints of the Java primitive data types.
Java中使用操作符+、-、*、/和%的算术操作受到Java基本数据类型的约束。
This means that if you can't fit your desired numbers into the range of, say a double
or long
then you'll have to use a "big number" library, such as the one built-in to Java (BigDecimal, BigInteger), or a third-party library, or write your own. This also means that you cannot use the arithmetic operators since Java does not support operator overloading.
这意味着,如果您无法将所需的数字放入双精度或长精度的范围内,那么您将不得不使用“big number”库,例如Java内置的库(BigDecimal, BigInteger)或第三方库,或者编写您自己的库。这也意味着您不能使用算术运算符,因为Java不支持操作符重载。
#4
1
Use the code below to multiply numbers of any length:-
使用下面的代码乘以任何长度的数字:-
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));
}
}
#5
0
strong text public class BigInteger {
强文本公共类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));
}
}
}
#6
0
When I want to do 90! or some other massive calculation, I try and use an int[] array, each element holding one of the digits. Then I apply the traditional multiplication we using pen and paper to get the answer in another int[] array.
当我想做90!或者其他大量的计算,我尝试使用int[]数组,每个元素都包含一个数字。然后我应用传统的乘法,我们使用钢笔和纸来得到另一个int[]数组的答案。
This is the code I wrote in Java which calculates 100! rather quickly. Feel free to use this however you like.
这是我在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;
}
#7
0
If we have really big numbers on which we want to perform arithmetic operations than they must be in some object form such as Strings.
如果我们有很大的数字我们想要进行算术运算它们必须是某种对象形式,比如字符串。
Let their be strings with the character length greater than the range of BigInteger.
让它们的字符串的字符长度大于BigInteger的范围。
In this case I'll perform arithmetic operation the way we do it on a notebook. For Example - Let's assume we have to do the addition. Start with comparing the two strings for length. Make three new Strings. The First String is the smaller one. The Second String is the rightmost substring of the longer string with length equal to the smaller string. The third string is the leftover long string from the left side. Now add the first and second string from the end converting characters to integers, one character at a time and keeping the carry in an int variable. Immediately after each addition, append the sum in a StringBuffer. After the two strings are added, do the same operation for the third string and keep on adding the carry. In the end reverse the StringBuffer and return the String.
在这种情况下,我将像在笔记本上那样执行算术运算。例如,假设我们必须做加法。从比较两个字符串的长度开始。让三个新的字符串。第一个弦是小的。第二个字符串是较长的字符串的最右边的子字符串,长度等于较小的字符串。第三个字符串是左边剩下的长字符串。现在,在将字符转换为整数的末尾添加第一个和第二个字符串,每次一个字符,并将进位保存在int变量中。在每次添加之后,立即在StringBuffer中追加和。在添加两个字符串之后,对第三个字符串执行相同的操作,并继续添加进位。最后,反转StringBuffer并返回字符串。
Here is the code I used for Addition
这是我用来添加的代码。
public String addNumber(String input1,String input2){
int n=0;String tempStr;
String one="";
String two="";
if(input1.length()>input2.length()){
n=input1.length()-input2.length();
tempStr=new String(input1);
one=new String(input1.substring(n,input1.length()));
two=new String(input2);
}else{
n=input2.length()-input1.length();
tempStr=new String(input2);
one=new String(input2.substring(n,input2.length()));
two=new String(input1);
}
StringBuffer temp=new StringBuffer();
for(int i=0;i<n;i++){
temp.append(tempStr.charAt(i));
}
StringBuffer newBuf=new StringBuffer();
int carry=0;
int c;
for(int i=one.length()-1;i>=0;i--){
int a=Character.getNumericValue(one.charAt(i));
int b=Character.getNumericValue(two.charAt(i));
c=a+b+carry;
newBuf.append(""+(c%10));
c=c/10;
carry=c%10;
}
String news=new String(temp);
for(int i=news.length()-1;i>=0;i--){
c=(Character.getNumericValue(news.charAt(i)))+carry;
newBuf.append(""+(c%10));
c=c/10;
carry=c%10;
}
if(carry==1){
newBuf.append(""+carry);
}
String newisis=new String(newBuf.reverse());
return newisis;
}