整數在計算機中的編碼
整數在計算機中是以補碼的形式存儲的, 補碼和原碼的區別在負數上。 下面代碼上展示了10這個整數在計算機中的二進制是怎麼表示的。
public static void main(String[] args) {
//1010 32位只輸出了4爲, 其他的28爲都爲零
System.out.println(Integer.toBinaryString(10));
//11111111111111111111111111110110
System.out.println(Integer.toBinaryString(-10));
}
針對負數的表示數字的含義, 可以用如下公式計算具體的數字:最高位取負1, 其他位取正, 整體想加, 就得到了負數的數字。如上面的-10可以用如下公式計算 -1 x 231 + 1 x 230 + .....+ 0 x 20。補碼錶示法有個好處是, 正負數可以直接做加法運算, 比如上面的正10和負10相加後, 就是0。
浮點數在計算機中的編碼
現實世界中, 無論是整數還是有理數都有無數個, 那如何在計算機中通過有限的存儲位數表示更多的數。32位能夠表示的整數是40億多點, 計算機中浮點數的本質採用的是科學計數法的方式來表述更多的數計算公式: (−1)s×1.f×2e。儲存結構如下圖所示:
。浮點數在編碼的時候需要注意的點
- 因爲存儲格式的原因, 我們在業務中存的浮點數有精度的缺失,在比較兩個浮點數是否相等的時候, 不能用“==”進行比較。
public static void testEqual(){
float f1 = 0.3f;
float f2 = 0.6f;
float f3 = f1 + f2;
//注意這裏不能用等於好, 大概率是不等於的
if(f3 == 0.9f){
System.out.println("f3 equal 0.9");
}
}
- 浮點數在做加法運算的時候是先對其, 再計算, 在對其的過程中, 需要把指數位較小的數通過移位操作, 讓數據的指數相同, 這裏就會有進度損失, 出現大數喫小數的情況。
public static void testPrecision(){
float a = 2000 * 10000f;
float b = 1.0f;
float c = a + b;
System.out.println("c is " + c);
float d = c - a;
System.out.println("d is " + d);
}
上面的情況經常出現在機器學習的場景下,機器學習要計算海量樣本,於是會出現幾億個浮點數的相加。每個浮點數可能都差不多大,但是隨着累積值的越來越大,就會出現“大數喫小數”的情況。
public static void testPrecision2(){
float sum = 0.0f;
for (int i = 0; i < 2000 * 10000; i++) {
float x = 1.0f;
sum += x;
}
System.out.println("sum is " + sum);
}
解決方法Kahan Summation 算法:
public static void kahanSummation(){
float sum = 0.0f;
float delta = 0;
for (int i=0; i<20000000; i++)
{
float addNum = 1.0f; // 需要加進去的數字
float totalNum = addNum - delta; // 當前加進去的數字和上次計算的誤差之和(這裏的delta必定是負值)
float tmp = sum + totalNum;
delta = tmp - sum - totalNum; // 計算丟失的精度
sum = tmp;
}
System.out.println("sum is " + sum);
}