[英]Double in place of Float and Float rounding
編輯:這個問題包括兩個主題:
有什么原因為什么我不應該總是使用Java double而不是float?
我問這個問題是因為使用浮點數時此測試代碼失敗,並且不清楚為什么,因為唯一的區別是使用浮點數而不是雙精度數。
public class BigDecimalTest {
@Test public void testDeltaUsingDouble() { //test passes
BigDecimal left = new BigDecimal("0.99").setScale(2,BigDecimal.ROUND_DOWN);
BigDecimal right = new BigDecimal("0.979").setScale(2,BigDecimal.ROUND_DOWN);
Assert.assertEquals(left.doubleValue(), right.doubleValue(), 0.09);
Assert.assertEquals(left.doubleValue(), right.doubleValue(), 0.03);
Assert.assertNotEquals(left.doubleValue(), right.doubleValue(), 0.02);
Assert.assertNotEquals(left.doubleValue(), right.doubleValue(), 0.01);
Assert.assertNotEquals(left.doubleValue(), right.doubleValue(), 0.0);
}
@Test public void testDeltaUsingFloat() { //test fails on 'failing assert'
BigDecimal left = new BigDecimal("0.99").setScale(2,BigDecimal.ROUND_DOWN);
BigDecimal right = new BigDecimal("0.979").setScale(2,BigDecimal.ROUND_DOWN);
Assert.assertEquals(left.floatValue(), right.floatValue(), 0.09);
Assert.assertEquals(left.floatValue(), right.floatValue(), 0.03);
/* failing assert */ Assert.assertNotEquals(left.floatValue() + " - " + right.floatValue() + " = " + (left.floatValue() - right.floatValue()),left.floatValue(), right.floatValue(), 0.02);
Assert.assertNotEquals(left.floatValue(), right.floatValue(), 0.01);
Assert.assertNotEquals(left.floatValue(), right.floatValue(), 0.0);
}}
失敗消息:
java.lang.AssertionError: 0.99 - 0.97 = 0.01999998. Actual: 0.9900000095367432
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failEquals(Assert.java:185)
at org.junit.Assert.assertNotEquals(Assert.java:230)
at com.icode.common.BigDecimalTest.testDeltaUsingFloat(BigDecimalTest.java:34)
知道為什么這個測試失敗了,為什么我不應該總是使用double而不是float? 當然,除了double之外,還有一個原因比float寬。
編輯:有趣的是,在兩種情況下Assert.assertNotEquals(double,double,delta)都需要double,因此失敗測試中返回的浮點數無論如何都會擴大為double,那么為什么測試失敗呢?
編輯:可能與此其他問題有關,但不確定: 十六進制不相同
編輯:從這個問題的答案十六進制不相同 ,可以得出結論,對於相同值,.99的float的科學表示形式IEEE 754與double有所不同。 這是由於四舍五入。
因此我們得到:
由於上述單元測試中的最大增量為0.02,而0.01999998(在失敗的測試中)低於該增量值,這意味着數字被視為相同,但是測試斷言它們沒有因此失敗。
你們都同意這一切嗎?
BigDecimal的文檔未提及floatValue()
如何取floatValue()
。 我認為它使用的是從最近到最近的關系,甚至是到平均的關系。
left
和right
分別設置為.99和.97。 將這些值在最近舍入模式下轉換為double
時,結果為0.98999999999999999999911182158029987476766109466552734375(以十六進制浮點數表示,0x1.fae147ae147aep-1)和0.9699999999999999733546474962430298328399658203125(0x1.f0a3d70a3d70ap-1)。 減去這些后,結果為0.020000000000000017763568394002504646778106106453453,明顯超過.02。
將.99和.97轉換為float
,結果為0.9900000095367431640625(0x1.fae148p-1)和0.9700000286102294921875(0x1.f0a3d8p-1)。 減去這些后,結果為0.019999980926513671875,顯然小於.02。
簡而言之,當十進制數字轉換為浮點數時,舍入可能會向上或向下。 這取決於相對於最接近的可表示浮點值而言,數字恰好位於何處。 如果不對其進行控制或分析,則實際上是隨機的。 因此,有時您最終獲得的價值比您預期的要大,而有時您最終獲得的價值卻更低。
使用double
而不是float
不能保證不會發生與上述類似的結果。 碰巧的是,在這種情況下, double
float
值超出了精確的數學值,而float
值沒有超過。 對於其他數字,情況可能恰恰相反。 例如,使用double
, .09-.07
小於.02,但是使用float
,.09f-.07f`大於.02。
. 關於如何處理浮點算術,有很多信息,例如 。 這是一個太大的主題,無法在Stack Overflow問題中涵蓋。 有關於它的大學課程。
通常在當今的典型處理器上,使用double
而不是float
幾乎沒有額外的花費; 簡單的標量浮點運算以幾乎相同的速度執行double
和float
。 當數據量太大(從磁盤到內存或從內存到處理器)的傳輸時間變得很重要,或者它們在磁盤上占據的空間變大,或者您的軟件使用處理器的SIMD功能時,就會出現性能差異。 (SIMD允許處理器執行對多個數據片段的同樣的操作,在平行的。當前處理器通常用於提供關於帶寬的兩倍float
SIMD操作作為用於double
SIMD操作或不提供double
SIMD操作的。)
Double可以代表具有更大有效數字的數字,具有更大的范圍,反之亦然。 就CPU而言,雙重計算的成本更高。 因此,這完全取決於您的應用程序。 二進制數字不能完全代表1/5等數字。 這些數字最終會被四舍五入,從而引入錯誤,這些錯誤可在斷言失敗的根源上確定。 有關更多詳細信息,請參見http://en.m.wikipedia.org/wiki/Floating_point 。
[EDIT]如果所有其他方法均失敗,請運行基准測試:
package doublefloat;
/**
*
* @author tarik
*/
public class DoubleFloat {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
long t1 = System.nanoTime();
double d = 0.0;
for (long i=0; i<1000000000;i++) {
d = d * 1.01;
}
long diff1 = System.nanoTime()-t1;
System.out.println("Double ticks: " + diff1);
t1 = System.nanoTime();
float f = 0.0f;
for (long i=0; i<1000000000;i++) {
f = f * 1.01f;
}
long diff2 = System.nanoTime()-t1;
System.out.println("Float ticks: " + diff2);
System.out.println("Difference %: " + (diff1 - diff2) * 100.0 / diff1);
}
}
輸出:
Double ticks: 3694029247
Float ticks: 3355071337
Difference %: 9.175831790592209
該測試是在裝有Intel Core 2 Duo的PC上運行的。 請注意,由於我們僅在緊密循環中處理單個變量,因此無法淹沒可用的內存帶寬。 實際上,其中一個核心一直在每次運行中始終顯示100%CPU。 結論:差異為9%,實際上可以忽略不計。
第二個測試涉及相同的測試,但是分別使用相對較大的內存140MB和280MB分別用於float和double:
package doublefloat;
/**
*
* @author tarik
*/
public class DoubleFloat {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
final int LOOPS = 70000000;
long t1 = System.nanoTime();
double d[] = new double[LOOPS];
d[0] = 1.0;
for (int i=1; i<LOOPS;i++) {
d[i] = d[i-1] * 1.01;
}
long diff1 = System.nanoTime()-t1;
System.out.println("Double ticks: " + diff1);
t1 = System.nanoTime();
float f[] = new float[LOOPS];
f[0] = 1.0f;
for (int i=1; i<LOOPS;i++) {
f[i] = f[i-1] * 1.01f;
}
long diff2 = System.nanoTime()-t1;
System.out.println("Float ticks: " + diff2);
System.out.println("Difference %: " + (diff1 - diff2) * 100.0 / diff1);
}
}
輸出:
Double ticks: 667919011
Float ticks: 349700405
Difference %: 47.64329218950769
內存帶寬不堪重負,但我仍然可以看到CPU在短時間內達到100%的峰值。
結論:該基准測試在某種程度上證實了使用double花費的時間比CPU密集型應用程序的浮動時間多9%,而對數據密集型應用程序的浮動時間則多50%。 它還證實了Eric Postpischil的注釋,與有限的內存帶寬對性能的影響相比,CPU開銷相對可以忽略不計(9%)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.