[英]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.");
}
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(见下文)添加到基准测试中,这在我的机器上给出了以下时间:
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.