簡體   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