繁体   English   中英

如何在Java中使用双位代码和doubleToLongBits()?

[英]How to work with double bit codes and doubleToLongBits() in Java?

我能够得到双数的二进制代码:

Double d = 1.5E12;
long l = Double.doubleToLongBits(d);
String bin = Long.toBinaryString(l);
System.out.println(bin);
System.out.println(bin.length());

但是,生成的代码的行为不像双位代码,也没有任何意义:

1.5E12   --> 100001001110101110100111110111101111001100000000000000000000000 (length: 63)
-1.5E12  --> 1100001001110101110100111110111101111001100000000000000000000000 (length: 64)
1.5E-12  --> 11110101111010011000110110011001000001110001001101111100011010 (length: 62)
-1.5E-12 --> 1011110101111010011000110110011001000001110001001101111100011010 (length: 64)

我试图通过将它们分组来了解这些数字:

1.5E12   -->     10000100111  0101110100111110111101111001100000000000000000000000
-1.5E12  --> 1   10000100111  0101110100111110111101111001100000000000000000000000
1.5E-12  -->   (0)1111010111  1010011000110110011001000001110001001101111100011010
-1.5E-12 --> 1   01111010111  1010011000110110011001000001110001001101111100011010

首先,它为负号添加/删除了一些(这是一件坏事……说到解析器)。 但是更重要的是,该指数显示了非常可疑的数字:例如10000100111应该是1035(= 1023 + 12)而不是1063(1063-1023 = 40!),而(0)1111010111应该是1011(= 1023-12) 983(983-1023 = -40!)。

有谁知道如何读取此两位代码? 即如何从上述位代码中获取正确的指数和螳螂?

(示例:如何从位代码中再次获取值1.5E12?100001001110101110100111110111101111001100000000000000000000000-?-> 1.5E12)

更新:

使用Java API中的掩码,我最终得到了这样的值:

static final long SIGN = 0x8000000000000000L;
static final long EXPN = 0x7ff0000000000000L;
static final long SGNF = 0x000fffffffffffffL;

Double d = ...;
long lng = Double.doubleToLongBits(d);
String bin = Long.toBinaryString(lng);
long sign = (lng & SIGN) >>> (bin.length()-1);
long expn = (lng & EXPN) >>> 52;
long sgnf = lng & SGNF;

而且我可以轻松地打印它:

System.out.println("sign-bin: "+Long.toBinaryString(sign));
System.out.println("expn-bin: "+Long.toBinaryString(expn));
System.out.println("sgnf-bin: "+Long.toBinaryString(sgnf));
System.out.println("sign-string: "+Long.toString(sign));
System.out.println("expn-string: "+Long.toString(expn));
System.out.println("sgnf-string: "+Long.toString(sgnf));

使用Double 1.5E12,我得到了以下结果:

sign-bin: 0
expn-bin: 10000100111
sgnf-bin: 101110100111110111101111001100000000000000000000000
sign-string: 0
expn-string: 1063
sgnf-string: 1640400372629504

您知道如何从中获取“实际”十进制值吗? (例如1640400372629504-> 1.5和1063-> E12)

符号的0为“缺失”只是数字书写约定。

在十进制系统中,您将输入1067而不是0001067,对吗? 这就是Java所做的。 内存中存在0,只是不显示它们,因为您不需要第四个数字。


这也是二进制数的浮点表示。

如果将1.5 * 10 ^ -12转换为二进制数,则开头将有很多0(我开始了,但随后我的论文结束了-开头肯定有12个以上的0)。 然后将该二进制数归一化(以便在该点之前只有一个1),并将此归一化的指数用作指数。 我猜1.5 * 10 ^ -12的二进制指数确实是40。

换句话说:IEEE数字的指数表示值2 ^指数,而不是10 ^指数,就像我们在十进制中使用它一样。


根据此页面,您需要将螳螂的每个数字乘以2的相应幂,从2 ^ -1开始,然后是2 ^ -2,依此类推,然后将它们相加。

然后使用以下公式:

(-1)^(符号位)*(1+分数)* 2 ^(指数-偏差)

其中fraction是您从螳螂计算出的数字。 exponentexponent的十进制表示形式。 bias取决于您的精度,在您的情况下为双精度,即1023。对于单精度,只有127。


您不能将二进制表示形式的部分(例如二进制指数)直接转换为十进制对应部分(十进制指数)。 至少我不知道怎么做。

因此,您需要将完整的数字转换为十进制,然后将其拆分为数字及其指数。

这是将二进制表示形式转换为十进制的Java代码:

static final long SIGN = 0x8000000000000000L;
static final long EXPN = 0x7ff0000000000000L;
static final long SGNF = 0x000fffffffffffffL;

public static void main(String[] args){
    Double d = -0.0000000000015;//1500000000000d;
    long lng = Double.doubleToLongBits(d);
    String bin = Long.toBinaryString(lng);
    long sign = (lng & SIGN) >>> (bin.length()-1);
    long expn = (lng & EXPN) >>> 52;
    long sgnf = lng & SGNF;



    System.out.println("sign-bin: "+Long.toBinaryString(sign));
    System.out.println("expn-bin: "+Long.toBinaryString(expn));
    System.out.println("sgnf-bin: "+Long.toBinaryString(sgnf));
    System.out.println("sign-string: "+Long.toString(sign));
    System.out.println("expn-string: "+Long.toString(expn));
    System.out.println("sgnf-string: "+Long.toString(sgnf));

    String mantisse = Long.toBinaryString(sgnf);
    int pow2 = 2;
    double fraction = 0;
    for(int i = 0; i < mantisse.length(); i++){
        if(mantisse.charAt(i) == '1'){
            double curr = 1.0/pow2;
            fraction += Double.isInfinite(curr)? 0: curr;
        }
        //System.out.println(fraction + " " + pow2);
        pow2 <<= 1;
    }

    System.out.println((1+fraction));
    System.out.println("Back to decimal: " + (sign == 1?(-1):1) * (1+fraction) * Math.pow(2, expn - 1023));
}

请注意,由于计算机中的浮点算术不准确,并且存储信息的空间有限,因此结果并不完全准确。

我不知道如何提取数字,它是从双精度指数中提取的。 我最好的猜测是操纵String表示链接此:

String[] number = Double.toString(dBack).split("E");
System.out.println("Decimal exponent: " + (number.length == 2?number[1]: 1));
System.out.println("Decimal mantisse: " + number[0]);

希望对您有所帮助。

就像Wikipedia页面上所说,数字的值是

IEEE 488双精度数字值的表达式

因此二元指数为1063-1023 =40。这是有道理的,因为它将尾数乘以2 ^ 40,大约是10 ^ 12(因为2 ^ 10大约是1000,所以2 ^ 40将是1000 ^ 4)。 (这种对指数进行编码的方法称为零偏移。使用它而不是2s补码,因为它可以简化硬件。)

尾数的原始位是0101110100111110111101111001100000000000000000000000,因此在上面的公式说的1之前,我们有:

1.0101110100111110111101111001100000000000000000000000

现在,将二进制点右移40位以说明指数,我们可以:

10101110100111110111101111001100000000000.000000000000

令人高兴的是,十进制是整数1500000000000,正是我们想要的。

由此您应该能够获得算法。 第一步是计算整数部分(就像我在上面所做的那样),并使用标准算法打印整数:

i = 0;
while (int_part > 0) {
   digit[i] = int_part % 10
   int_part = int_part / 10
   i = i + 1
}
if (i == 0) return '0'
else reverse digits[0..i-1] and return

剩下分数位。 如果有N位,则将它们视为N位整数并除以2 ^ N。 例如,1的1位小数是1/2。 您可以通过执行长除法来获取十进制数字。 例如,二进制.11是3/4。 实施长除法,我们首先得到3 * 10/4 = 7R2。 然后2 * 10/4 = 7R0。 因此,十进制数字为.75。 用伪代码:

num = frac_part
den = 2 ^ (number of bits in frac_part)
i = 0
do {
  num = num * 10
  digit[i] = num / den
  num = num % den
  i = i + 1
} while (num != 0)

注意,这些算法是概念性的,而不是实用的。 一方面,它们假定任意精度的整数。 实际上,您不需要这种开销,因此计算实际上是在浮点中完成的。 沃思(Wirth)的老书《 算法+数据结构=程序》深入探讨了它的工作原理。

如果要实现printf或类似的库函数,则基本转换的细节非常繁琐,无法快速获得正确的结果。 “正确”是指能够以10为底打印一个数字,并完全放心地读回该表示形式,以确保您获得完全相同的数字。 不要小看这个问题。

而且,正如其他人所说的那样,您的Java打印例程只是消除了所有的前导零,因为它们是为人而设计的,人们通常并不关心前导零。

暂无
暂无

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

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