如何在不使用字符串的情况下删除整数的第 n 个十六进制数字?

How to remove the nth hexadecimal digit of a integer number without using Strings?

考虑一个十六进制整数值,例如 n = 0x12345,如何通过执行 remove(n, 3)(大端)得到 0x1235 作为结果?

对于上面的输入,我认为这可以通过执行一些按位步骤来实现:

但是我仍然对如何实现它感到困惑,一旦每个十六进制数字占用 4 个空格。另外,我不知道检索数字长度的好方法。

这可以使用字符串轻松完成,但是我需要在数千次迭代的上下文中使用它,并且我认为选择字符串不是一个好主意。

那么,没有字符串删除的好方法是什么?

解决方案:

将使用 10 的操作替换为使用 16 的操作。

演示

使用按位运算符:

public class Main {
    public static void main(String[] args) {
        int n = 0x12345;
        int temp = n;
        int length = 0;

        // Find length
        while (temp != 0) {
            length++;
            temp /= 16;
        }
        System.out.println("Length of the number: " + length);

        // Remove digit at index 3
        int m = n;
        int index = 3;
        for (int i = index + 1; i <= length; i++) {
            m /= 16;
        }
        m *= 1 << ((length - index - 1) << 2);
        m += n % (1 << ((length - index - 1) << 2));
        System.out.println("The number after removing digit at index " + index + ": 0x" + Integer.toHexString(m));
    }
}

输出:

Length of the number: 5
The number after removing digit at index 3: 0x1235

使用Math::pow:

public class Main {
    public static void main(String[] args) {
        int n = 0x12345;
        int temp = n;
        int length = 0;

        // Find length
        while (temp != 0) {
            length++;
            temp /= 16;
        }
        System.out.println("Length of the number: " + length);

        // Remove digit at index 3
        int m = n;
        int index = 3;
        for (int i = index + 1; i <= length; i++) {
            m /= 16;
        }
        m *= ((int) (Math.pow(16, length - index - 1)));
        m += n % ((int) (Math.pow(16, length - index - 1)));
        System.out.println("The number after removing digit at index " + index + ": 0x" + Integer.toHexString(m));
    }
}

输出:

Length of the number: 5
The number after removing digit at index 3: 0x1235

JavaScript版本:

n = parseInt(12345, 16);
temp = n;
length = 0;

// Find length
while (temp != 0) {
    length++;
    temp = Math.floor(temp / 16);
}
console.log("Length of the number: " + length);

// Remove digit at index 3
m = n;
index = 3;
for (i = index + 1; i <= length; i++) {
    m = Math.floor(m / 16);
}
m *= 1 << ((length - index - 1) << 2);
m += n % (1 << ((length - index - 1) << 2));
console.log("The number after removing digit at index " + index + ": 0x" + m.toString(16));

这是通过编写一个从右侧移除的方法但调整参数以从左侧移除来实现的。好处是从右边移除也可以使用。此方法使用 longs 来最大化十六进制值的长度。

long n = 0x12DFABCA12L;
int r = 3;
System.out.println("Supplied value: " + Long.toHexString(n).toUpperCase());
n = removeNthFromTheRight(n, r);
System.out.printf("Counting %d from the right: %X%n", r, n);
n = 0x12DFABCA12L;
n = removeNthFromTheLeft(n, r);
System.out.printf("Counting %d from the left:  %X%n", r, n);

版画

Supplied value: 12DFABCA12
Counting 3 from the right: 12DFABA12
Counting 3 from the left:  12DABCA12

这是通过递归地从末尾删除一个数字直到您要删除的数字之前的数字来工作的。然后通过调用堆栈删除它和 return,用原始值重建数字。

此方法从右数。

public static long removeNthFromTheRight(long v, int n) {
    if (v <= 0) {
        throw new IllegalArgumentException("Not enough digits");
    }
    // save hex digit
    long k = v % 16;
    while (n > 0) {
    // continue removing digit until one
    // before the one you want to remove
    return removeNthFromTheRight(v / 16, n - 1) * 16 + k;
}
if (n == 0) {
    // and ignore that digit.
    v /= 16;
}
return v;
}

此方法从左数起。它只是调整 n 的值,然后调用 removeFromTheRight.

public static long removeNthFromTheLeft(long v, int n) {
    ndigits = (67-Long.numberOfLeadingZeros(v))>>2;

    // Now just call removeNthFromTheRight with modified paramaters.
    return removeNthFromTheRight(v, ndigits - n - 1);
}

这是我使用位操作的版本,并附有解释。

  • 设置的最高位有助于找到掩码的偏移量。对于 long,该位是 64——前导零的数量。要得到十六进制数字的个数,必须除以 4。要计算可被 4 整除的数字,需要在除法前加 3。所以这使得位数:

    digits = (67-Long.numberOfLeadingZeros(i))>>2;
    然后需要对其进行调整以屏蔽数字的适当部分。

    offset = digits-i - 1

  • m 是屏蔽要删除的数字的掩码。所以从 -1L (all hex 'F') 开始并右移 4*(16-offset) 位。这将生成一个掩码,将要删除的数字右侧的所有内容都屏蔽掉。 注意:如果 offset0,则移位运算符将为 64,并且不会移位任何位。为了适应这一点,移位操作分为两个操作。

  • 现在简单地屏蔽掉低位 v & m

  • 并将高位右移4位以消除所需的数字。 (v>>>4)^ ~m
  • 然后将这两部分进行简单的“或”运算。
    static long remove(long v, int i) {
        int offset = ((67 - Long.numberOfLeadingZeros(v))>>2) - i - 1;
        long m = (-1L >>> (4*(16 - offset) - 1)) >> 1;
        return ((v >>> 4) & ~m) | (v & m);
    }

与您描述的想法类似,这可以通过为上部和下部创建遮罩,移动上部,然后重新组装来完成。

int remove(int x, int i) {
    // create a mask covering the highest 1-bit and all lower bits
    int m = x;
    m |= (m >>> 1);
    m |= (m >>> 2);
    m |= (m >>> 4);
    m |= (m >>> 8);
    m |= (m >>> 16);
    // clamp to 4-bit boundary
    int l = m & 0x11111110;
    m = l - (l >>> 4);
    // shift to select relevant position
    m >>>= 4 * i;
    // assemble result
    return ((x & ~(m << 4)) >>> 4) | (x & m);
}

其中“>>>”是一个无符号移位。

请注意,如果 0 表示独立于输入的 32 位字中的最高十六进制数字,则这要简单得多:

int remove(int x, int i) {
    int m = 0xffffffff >>> (4*i);
    return ((x & ~m) >>> 4) | (x & (m >>> 4));
}