Search code examples
javabigintegerunsignedbase36

How do you convert a Java long to an *unsigned* base-X String (and back)?


[EDIT] I am NOT accepting any answer which involves BigInteger, or other similarly inefficient method. Please actually read the question before answering!

Java, annoyingly enough, does not support unsigned number types. You can convert a byte, short or int to unsigned, by using the next bigger type, for example:

short s = -10;
int unsigned_short = s & 0xFFFF;

But you cannot do this with long, since there is no bigger type.

So, how do you convert a signed long into an "unsigned" base-X, in my case base-36, and back? The Long class has those methods, but treat longs as signed, simply because they are.

I could probably do that using some manipulation and BigInteger, but BigInteger is incredibly slow, and creates garbage through temporary BigInteger creation. And I'm going to be doing a lot of those conversions (I think). I need an algorithm that is as efficient as the default implementation of Long.toString(long i, int radix).

Trying to adapt the code of Long.toString() I come to:

final int RADIX = 36;
final char[] DIGITS = { '0', ... , 'Z' };
long value = 100;
if (value == 0) {
    return "0";
} else {
    char[] buf = new char[13];
    int charPos = 12;
    long i = value;
    while (i != 0) {
        buf[charPos--] = DIGITS[Math.abs((int) (i % RADIX))];
        i /= RADIX;
    }
    return new String(buf, charPos + 1, (12 - charPos));
}

But it does not handle negative values correctly, despite the Math.abs().

Once this works, I need the reverse conversion, but I'm hoping it will be easier. Your welcome to put it in your answer too.

[EDIT] Actually, I just looked at the code for Long.parseLong(String s, int radix), and it looks more complicated than Long.toString(long i, int radix).


Solution

  •     long l = 0xffffffffffffffffL; // any long, e.g. -1
    
        // to string
        BigInteger bi = new BigInteger(Long.toString(l & ~(1L << 63)));
        if (l < 0) bi = bi.setBit(64);
        final String b36 = bi.toString(36);
        System.out.println("original long:" + l);
        System.out.println("result 36: " + b36);
    
        // parse
        final BigInteger parsedBi = new BigInteger(b36, 36);
    
        l = parsedBi.longValue();
        if (parsedBi.testBit(64)) l = l | (1L << 63);
        System.out.println("parsed long = " + l);
    

    Benchmarking (one million operations):

        // toString
        long l = 0x0ffffffffffffeffL;
        {
            final long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) toStringBi(l);
            System.out.println("BigInteger time = " + 
                (System.currentTimeMillis() - start) + " ms.");
        }
        {
            final long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) Long.toString(l, 36);
            System.out.println("Long.toString time = " + 
               (System.currentTimeMillis() - start) + "ms.");
        }
        // Parsing
        final String b36 = toStringBi(l);
        final String long36 = Long.toString(l, 36);
        {
            final long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) {
                final BigInteger parsedBi = new BigInteger(b36, 36);
                l = parsedBi.longValue();
                if (parsedBi.testBit(64)) l = l | (1L << 63);
            }
            System.out.println("BigInteger.parse time = " 
                + (System.currentTimeMillis() - start) + " ms.");
        }
        {
            final long start = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) Long.parseLong(long36, 36);
            System.out.println("Long.parseLong time = " 
                + (System.currentTimeMillis() - start) + "ms.");
        }
    
    • BigInteger time = 1027 ms.
    • Long.toString time = 244ms.
    • BigInteger.parse time = 297 ms.
    • Long.parseLong time = 132ms.