简体   繁体   English

如何将Java long转换为* unsigned * base-X String(并返回)?

[英]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. [编辑]我不接受任何涉及BigInteger或其他类似低效方法的答案。 Please actually read the question before answering! 请在回答之前先阅读问题!

Java, annoyingly enough, does not support unsigned number types. 令人讨厌的是,Java不支持无符号数字类型。 You can convert a byte, short or int to unsigned, by using the next bigger type, for example: 您可以使用下一个更大的类型将byte,short或int转换为unsigned,例如:

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? 那么,如何将一个签名的long转换为“unsigned”base-X,在我的情况下是base-36,然后返回? The Long class has those methods, but treat longs as signed, simply because they are. Long类具有这些方法,但将longs视为已签名,仅仅因为它们是。

I could probably do that using some manipulation and BigInteger, but BigInteger is incredibly slow , and creates garbage through temporary BigInteger creation. 我可以使用一些操作和BigInteger来做到这一点,但BigInteger 非常慢 ,并通过临时BigInteger创建创建垃圾。 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). 我需要一个与Long.toString(long i,int radix)的默认实现一样高效的算法。

Trying to adapt the code of Long.toString() I come to: 试图调整Long.toString()的代码我来:

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(). 但它没有正确处理负值,尽管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). [编辑]实际上,我只看了Long.parseLong(String s,int radix)的代码,它看起来比Long.toString(long i,int radix) 复杂。

    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. BigInteger时间= 1027毫秒。
  • Long.toString time = 244ms. Long.toString时间= 244ms。
  • BigInteger.parse time = 297 ms. BigInteger.parse time = 297 ms。
  • Long.parseLong time = 132ms. Long.parseLong time = 132ms。

Another option is to use UnsignedLongs from the Google guava-libraries (which have lots of other goodies as well): 另一个选择是使用来自Google guava库的 UnsignedLongs (它还有许多其他好东西):

String s = UnsignedLongs.toString( -1L, Character.MAX_RADIX );

and

long l = UnsignedLongs.parseUnsignedLong( "2jsu3j", 36 );

Added to the benchmark from +EugeneRetunsky (see below) this gives the following times on my machine: 从+ EugeneRetunsky(见下文)添加到基准测试中,这在我的机器上给出了以下时间:

  • BigInteger time (1st run) = 1306 ms. BigInteger时间(第一次运行)= 1306 ms。
  • BigInteger time (2nd run) = 1075 ms. BigInteger时间(第二次运行)= 1075 ms。
  • Long.toString time = 422ms. Long.toString时间= 422ms。
  • UnsignedLongs.toString time = 445ms. UnsignedLongs.toString时间= 445ms。
  • BigInteger.parse time = 298 ms. BigInteger.parse time = 298 ms。
  • Long.parseLong time = 164ms. Long.parseLong time = 164ms。
  • UnsignedLongs.parseUnsignedLong time = 107ms. UnsignedLongs.parseUnsignedLong time = 107ms。

Out of curiosity, I let the first test run twice to check if that would improves the time. 出于好奇,我让第一次测试运行两次以检查是否会改善时间。 It consistently does (to ~400ms on my machine), also for the case of UnsignedLongs. 它始终如一(在我的机器上约为400毫秒),也适用于UnsignedLongs。 The other options do not seem to profit any more from the hot-spot compiler. 其他选项似乎不再从热点编译器中获利。

public class UnsignedLongsTest {
private static String toStringBi( long l ) {
    BigInteger bi = new BigInteger(Long.toString(l & ~(1L << 63)));
    if (l < 0) {
        bi = bi.setBit(64);
    }
    final String b36 = bi.toString(36);
    return b36;
}

public static void main( String[] args ) {
    // toString
    long l = 0x0ffffffffffffeffL;
    {
        final long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            toStringBi(l);
        }
        System.out.println("BigInteger time (1st run) = " +
                (System.currentTimeMillis() - start) + " ms.");
    }
    {
        final long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            toStringBi(l);
        }
        System.out.println("BigInteger time (2nd run) = " +
                (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.");
    }
    {
        final long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            UnsignedLongs.toString(l, 36);
        }
        System.out.println("UnsignedLongs.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.");
    }
    {
        final long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            UnsignedLongs.parseUnsignedLong( long36, 36 );
        }
        System.out.println("UnsignedLongs.parseUnsignedLong time = "
                + (System.currentTimeMillis() - start) + "ms.");
    }
}

Since despite "NOT accepting any answer which involves BigInteger", you accepted a BigInteger solution, here is an alternate BigInteger solution. 因为尽管“不接受任何涉及BigInteger的答案”,但您接受了BigInteger解决方案,这是另一种BigInteger解决方案。 Rather than masking off the sign, you can force the signum to always positive: 您可以强制签名始终为正,而不是遮盖标志:

long input = 0xffffffffffffffffL; // any long, e.g. -1
byte[] bytes = ByteBuffer.allocate(8).putLong(input).array();

String base36 = new BigInteger(1, bytes).toString(36);

Also, if you are working with the long as a byte array, @JonnyDee has an algorithm (in Python but it's short) for converting between any two bases which is applicable here if you consider the byte array to be a number with Base-256 digits. 另外,如果你使用long作为一个字节数组,@ JonnyDee有一个算法(用Python但它很简短),用于在任何两个基础之间进行转换,如果你认为字节数组是一个Base-256的数字,这里适用数字。 Converting back to bytes is just converting base-36 to base-256. 转换回字节只是将base-36转换为base-256。

https://stackoverflow.com/a/6158278/43217 https://stackoverflow.com/a/6158278/43217

And his corresponding blog post: 他的相应博客文章:

https://jonnydee.wordpress.com/2011/05/01/convert-a-block-of-digits-from-base-x-to-base-y/ https://jonnydee.wordpress.com/2011/05/01/convert-a-block-of-digits-from-base-x-to-base-y/

The problem is that you're looking for a fast unsigned 64-bit divmod given only a signed 64-bit divmod. 问题是你只需要一个有符号的64位divmod就可以找到一个快速无符号的64位divmod。 Searching for udivmoddi3 should give you a few implementations in C — these are typically used to do 64-bit divmod on architectures that only support 32-bit divmod in hardware. 搜索udivmoddi3应该会在C中为您提供一些实现 - 这些实现通常用于在仅支持硬件中的32位divmod的体系结构上执行64位divmod。

Note that you only need to grab the bottom digit — once you've done this, the quotient will be positive and you can use Long.toString(). 请注意,您只需要抓住底部数字 - 一旦完成此操作,商将为正数,您可以使用Long.toString()。

If the radix is even (you state base 36), you can get the bottom digit without too much hassle (my math may be wrong): 如果基数是偶数(你指出基数为36),你可以得到最低位数而没有太多麻烦(我的数学可能是错误的):

int bottomDigit = ((value>>>1)%(radix/2))<<1)|((int)value&1);
long rest = (value>>>1)/(radix/2);
if (rest == 0)
{
  return Integer.toString(bottomDigit,radix);
}
return Long.toString(rest,radix) + Integer.toString(bottomDigit,radix);

An obvious further optimization is to call Long.toString() directly if the value is positive. 显而易见的进一步优化是,如果值为正,则直接调用Long.toString()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM