Bug in BigInteger.add() or my math mistake?

Hello,

I'm trying to do some radix conversions on 128-bit numbers using BigInteger. I'm getting some wierd results, so I'm wondering if this is a bug in the JDK, or if I've made a mistake with my math (I'm on JDK 1.5.0_10-b03).

Here's what I'm doing (in English). Code follows below, too.

1.) Start off with two 'long' numbers (MSB & LSB).

2.) Create a BigInteger using MSB (the Most Sig Bits).

3.) Bitshift the newly created BigInt left by 64 bits.

4.) Add LSB to the BigInteger (The LSB is a 'long' number, so all 64 of its bits should be appended into the newly opened 64 bits of the BigInteger.

This works, except that if LSB is negative, the final 128bit BigInteger is incorrect. The last 2 MSB's of the BigInteger (i.e., bit 66 and 65 of the BigInteger, assuming the rightmost bit of the 128 bits is index 0) is 1 less than it should be.

In the test case below, bits 65 and 64 are '01' (decimal 1), while they should be '10' (decmial 2).

This error does not happen if the 2 baseline 'long' arguments (MSB and LSB) are both positive. I suspect this has something to do with the sign of the LSB arguement, but the sign bit should be bit 64 of the long number, and should just get added into the BigInteger without affecting the other 64 MSB bits.

In addition, if I create a BigInteger using the string constructor (where the string is a concatenation of 2 long numbers in hex format, e.g., then this discrepancy does not happen).

-

import java.math.BigInteger;

import junit.framework.TestCase;

public class BigIntegerCreationTestor extends TestCase

{

/**

* Does the test.

*/

public void testBigInteger()

{

long baselineMSB = 0x2a42940641c94eb2L;

long baselineLSB = 0xaf6578135552b65cL;

// long baselineLSB = 0x2a42940641c94eb2L;

this.testBigIntegerCreation(baselineMSB, baselineLSB);

/*

* for(int i = 1; i > 0; i++) { baselineMSB += 0xFFFFFFFFL; baselineLSB +=

* 0xFFFFFFFFL; this.testBigIntegerCreation(baselineMSB, baselineLSB); }

*/

}

/**

* Take two longs. Create a BigInteger with the first long (the Most Sig

* Bits). Bitshift the newly created BigInt left by 64 bits. Add the LSB

* long into to the BigInteger. The LSB is a 'long' number, so all 64 of its

* bits should be appended into the remaining 64 bits of the BigInteger.

*

* This works, except that if the LSB long value is negative, the final 128

* bit BigInteger is incorrect. The last 2 MSB's of the BigInteger (i.e.,

* bit 66 and 65 of the BigInteger, assuming the rightmost bit of the 128

* bits is index 0) is 1 less than it should be. In the test case below,

* bits 65 and 64 are '01' (decimal 1), while they should be '10' (decmial

* 2).

*

* This error does not happen if the 2 baseline 'long' arguments

* (baselineMSB and baselineLSB) are both positive. I suspect this has

* something to do with the sign of the LSB arguement, but the sign bit

* should be bit 64 of the long number, and should just get added into the

* BigInteger upto position 64, without affecting the MSB bits.

*/

private void testBigIntegerCreation(long baselineMSB, long baselineLSB)

{

System.out.println("baselineMSB(B10): " + baselineMSB);

System.out.println("baselineLSB(B10): " + baselineLSB);

String badBiggyInB2 = "";

// This works, and is fine, but we don't use it to demonstrate an error.

// BigInteger goodBiggy = new

// BigInteger("2a42940641c94eb2af6578135552b65c", 16);

String sMSB = Long.toBinaryString(baselineMSB);

while (sMSB.length() < 64)

{

sMSB = "0" + sMSB;

}

String sLSB = Long.toBinaryString(baselineLSB);

while (sLSB.length() < 64)

{

sLSB = "0" + sLSB;

}

assertTrue(sMSB.length() == 64);

assertTrue(sLSB.length() == 64);

BigInteger goodBiggy = new BigInteger((sMSB + sLSB), 2);

// The errant BigInteger Creation Process

BigInteger badBiggy = BigInteger.valueOf(baselineMSB);

badBiggyInB2 = badBiggy.toString(2);

System.out.println("Incorrect Half BigInteger(B2): " + badBiggyInB2);

System.out.println("Incorrect Half BigInteger(B16): "

+ badBiggy.toString(16));

System.out.println("baselineMSB(B2): "

+ Long.toBinaryString(baselineMSB));

System.out.println("Correct BigInteger(B16): "

+ Long.toHexString(baselineMSB));

badBiggy = badBiggy.shiftLeft(64);

badBiggyInB2 = badBiggy.toString(2);

System.out.println("Incorrect Almost Full BigInteger(B2): "

+ badBiggyInB2);

System.out.println("Correct BigInteger(B2): " + goodBiggy.toString(2));

badBiggy = badBiggy.add(BigInteger.valueOf(baselineLSB));

badBiggyInB2 = badBiggy.toString(2);

System.out.println("baselineLSB (B2): "

+ Long.toBinaryString(baselineLSB));

System.out.println("Incorrect Full BigInteger(B2): " + badBiggyInB2);

System.out.println("Correct BigInteger(B2): " + goodBiggy.toString(2));

System.out.println("Incorrect Full BigInteger(B16): "

+ badBiggy.toString(16));

System.out

.println("Correct BigInteger(B16): " + goodBiggy.toString(16));

System.out.println("Incorrect Full BigInteger(B32): "

+ badBiggy.toString(32));

System.out

.println("Correct BigInteger(B32): " + goodBiggy.toString(32));

if (!(badBiggy.equals(goodBiggy)))

{

System.out.println("TEST FAIL: baseline 'badBiggy' ("

+ badBiggy.toString(16)

+ ") is **NOT EQUAL** to 'goodBiggy' ("

+ goodBiggy.toString(16) + ") but should be.");

System.out.println("Difference in Hex is: "

+ goodBiggy.subtract(badBiggy).toString(16));

}

assertTrue(badBiggy.equals(goodBiggy));

System.out.print("PASS for MSB: " + baselineMSB + " and LSB: "

+ baselineLSB);

}

}

[6100 byte] By [fuellin3Fa] at [2007-11-26 14:15:14]
# 1
Sounds like you should be using [url= http://java.sun.com/j2se/1.4.2/docs/api/java/math/BigInteger.html#BigInteger(int,%20byte%5b%5d)]BigInteger(int, byte[])[/url]
tschodta at 2007-7-8 2:04:56 > top of Java-index,Other Topics,Algorithms...
# 2

Your approach looks a bit convoluted. I would use something like

public static BigInteger createFromTwoLongs(long baselineMSB, long baselineLSB)

{

byte[] resultBytes = new byte[16];

for (int i = 7; i >= 0; i--)

{

resultBytes[i+8] = (byte)baselineLSB;

baselineLSB >>>= 8;

resultBytes[i] = (byte)baselineMSB;

baselineMSB >>>= 8;

}

return new BigInteger(1, resultBytes);

}

sabre150a at 2007-7-8 2:04:56 > top of Java-index,Other Topics,Algorithms...
# 3

Sabre150, your approach does seem best. Thanks for posting that!

As for my original approach, I discovered what the problem was.

Assume the LSB 'long' is F000 0000 0000 0001.Because Java uses signed numbers, (point: the LSB in question is negative), then creating a BigInteger using LSB will create a BigInteger using the 2-complement representation of the above LSB number, which will actually be a negative BigInteger number (instead of a larger, positive BigInteger if using unsigned numbers)

Adding the now-negative LSB BigInteger to the original BigInteger as my algorithm did will throw the result off by 1 (via binary arithmetic), but only in the case where the LSB number is negative. Thus, my original algorithm is destined to fail for any LSB numbers that are negative.

I'm going to use Sabre150's approach, as that seems to work best.

fuellin3Fa at 2007-7-8 2:04:56 > top of Java-index,Other Topics,Algorithms...
# 4
what's wrong with using java.lang.Math.abs() on your longs LSB?as far as i understood the above, you're not calculating these MSB + LSB, but appending cyphers and don't at all need the negatives?Message was edited by: RoSeventy
RoSeventya at 2007-7-8 2:04:56 > top of Java-index,Other Topics,Algorithms...
# 5

Math.abs() will return the absolute value of the SIGNED long in question. In other words, it will return the incorrect binary number (behind the scenes).

For example, ABS(-72) is 72 in decimal, which in binary (signed or unsigned) is 0100 1000. However, the actual value we want is 184 unsigned, or -72 in signed binary, which is '1011 1000'.

Since Java uses signed numbers exclusively, there is a difference between what the base10 numbers appear to be, and what the binary equivalents are behind the scenes (depending on what you're expecting).

fuellin3Fa at 2007-7-8 2:04:56 > top of Java-index,Other Topics,Algorithms...