是否有更快的方法将十六进制小数部分转换为十进制小数?

2020年11月28日 69点热度 0条评论

我编写了一个程序,以十六进制生成pi的数字。我经常以基准值将我拥有的十六进制值转换为十进制值并将其保存到文件中。目前,我正在使用BigDecimal通过以下代码进行该数学运算:

private static String toDecimal(String hex) {
    String rawHex = hex.replace(".", "");
    BigDecimal base = new BigDecimal(new BigInteger(rawHex, 16));
    BigDecimal factor = new BigDecimal(BigInteger.valueOf(16).pow(rawHex.length() - 1));
    BigDecimal value = base.divide(factor);
    return value.toPlainString().substring(0, hex.length());
}

请注意,此方法仅适用于整数部分中包含一位的十六进制值(包括pi),请勿复制并粘贴该值以用于一般用途。

因此,此代码可以正常工作,但是对于最新的基准测试(250万位),转换需要11.3个小时才能完成。

有没有更快的手动方法?

我尝试将第一个小数位除以16,再将第二个小数除以16 ^ 2,依此类推,但是这样很快就会失去控制。也许可以通过某种方式将数字移回以保持除数很低?但是可能需要处理n + 1,n + 2,n + 3等数字以获得n的正确值。

解决方案如下:

首先,我认为您的函数toDecimal是错误的,例如,它无法正确转换输入".1a"(关闭了16倍),并为输入".800"引发了异常。第三行应为:

BigDecimal factor = new BigDecimal(BigInteger.valueOf(16).pow(rawHex.length()));

异常来自:

return value.toPlainString().substring(0, hex.length());

转换后的值可能比输入值短,您会得到一个
java.lang.StringIndexOutOfBoundsException

继续:


实际上,我尚未针对您当前的方法进行基准测试;我只是将此作为“深思熟虑”。在这里,我正在做乘法,因为孩子在学校被教做乘法。在您的情况下,我们有一个很大的循环只能产生一位数字。但是,如果可以通过某种方式使其适应使用BigDecimal(尚不清楚如何使用),则它可能比当前的方法要快(真正需要的是BigHexadecimal类)。

可以观察到,可以使用乘法将分数从一个基数转换为另一个基数。在这种情况下,我们具有以下十六进制分数(我们可以忽略整数部分,即在转换pi时为3):

.h1h2h3h4 ... hn

其中hn是第n个十六进制“半字节”。

我们希望将以上转换为以下十进制分数:

.d1d2d3d4 ... dn

其中dn是第n个十进制数字。

如果将两个数量乘以10,我们将得到:

h'1.h'2h'3h'4 ... h'n

质数(`)表示乘法之后我们有了全新的十六进制半字节值。



d1.d2d3d4 ... dn

乘以10只会将小数部分向左移动一位。

我们必须注意,小数点左边的数量必须相等,即d1 == h'1。因此,我们将十六进制分数重复乘以10,每次执行时,我们都会将整数部分作为下一个十进制数字进行转换。我们重复此过程,直到新的十六进制分数变为0或产生任意数目的十进制数字为止:


See Java Demo

class Test {

    private static String toDecimal(String hex, int numberDigits) {
        /* converts a string such as "13.1a" in base 16 to "19.1015625" in base 10 */
        int index = hex.indexOf('.');
        assert index != -1;
        StringBuilder decimal = new StringBuilder((index == 0) ? "" : String.valueOf(Integer.parseInt(hex.substring(0, index), 16)));
        decimal.append('.');
        int l = hex.length() - index - 1;
        assert l >= 1;
        int firstIndex = index + 1;
        int hexDigits[] = new int[l];
        for (int i = 0; i < l; i++) {
            hexDigits[i] = Integer.parseInt(hex.substring(i + firstIndex, i + firstIndex + 1), 16);
        }
        while (numberDigits != 0 && l != 0) {
            int carry = 0;
            boolean allZeroes = true;
            for (int i = l - 1; i >= 0; i--) {
                int value = hexDigits[i] * 10 + carry;
                if (value == 0 && allZeroes) {
                    l = i;
                }
                else {
                    allZeroes = false;
                    carry = (int)(value / 16);
                    hexDigits[i] = value % 16;
                }
            }
            numberDigits--;
            if (carry != 0 || (numberDigits != 0 && l != 0))
                decimal.append("0123456789".charAt(carry));
        }
        return decimal.toString();
    }

    public static void main(String[] args) {
        System.out.println(toDecimal("13.1a", 15));
        System.out.println(toDecimal("13.8", 15));
        System.out.println(toDecimal("13.1234", 15));
    }

}

印刷品:

19.1015625
19.5
19.07110595703125