[英]How to determine whether number is exactly representable in floating-point?
由於浮點數是基數為 2 的數字系統,因此不可能直接表示0.24F
,因為在沒有重復十進制周期的情況下,不可能在十進制系統中表示1/3
,即1/3=0.3333...
或0.(3)
。
因此,當打印回十進制表示時,浮點數0.24F
顯示為0.23
,但由於四舍五入而發生變化:
println(0.24F) => 0.23999999463558197021484375
而0.25F
可以直接顯示:
println(0.25F) => 0.25
但是我怎么能確定一個數字是完全可以表示的呢?
isExactFloat(0.25F) ==> true
isExactFloat(0.24F) ==> false
也許 Java API 已經有一些功能可以做到這一點?
UPD這是一個代碼,它顯示了 [-4, 4] 范圍內的浮點數及其內部表示:
public class FloatDestructure {
public static void main(String[] args) {
BigDecimal dec = BigDecimal.valueOf(-4000L, 3);
BigDecimal incr = BigDecimal.valueOf(1L, 3);
for (int i = 0; i <= 8000; i++) {
double dbl = dec.doubleValue();
floatDestuct(dbl, dec);
dec = dec.add(incr);
}
}
static boolean isExactFloat(double d) { return d == (float) d; }
static void floatDestuct(double val, BigDecimal dec) {
float value = (float) val;
int bits = Float.floatToIntBits(value);
int sign = bits >>> 31;
int exp = (bits >>> 23 & ((1 << 8) - 1)) - ((1 << 7) - 1);
int mantissa = bits & ((1 << 23) - 1);
float backToFloat = Float.intBitsToFloat((sign << 31) | (exp + ((1 << 7) - 1)) << 23 | mantissa);
boolean exactFloat = isExactFloat(val);
boolean exactFloatStr = Double.toString(value).length() <= 7;
System.out.println(dec.toString() + " " + (double) val + " " + (double) value + " sign: " + sign + " exp: " + exp + " mantissa: " + mantissa + " " + Integer.toBinaryString(mantissa) + " " + (double) backToFloat + " " + exactFloat + " " + exactFloatStr);
}
}
當尾數為零時,浮點數肯定是精確的。 但在其他情況下,如-0.375
或-1.625
則不太清楚。
一般來說,這是不可能的。 一旦將數字轉換為浮點數或雙精度數,它就只是數字的近似值。 因此,您對 isexactfloat() 的輸入將不准確...
如果您有精確版本的浮點數,例如字符串格式,那么可以設計一個函數來告訴您浮點數或雙精度數是否准確地表示字符串格式的數字。 請參閱下面 Carlos Heurberger 關於如何實現此類功能的評論。
從中創建一個BigDecimal
並捕獲java.lang.ArithmeticException
,如果存在非終止的十進制擴展,它將拋出該異常。
Java double
只能表示終止二進制分數。 轉換為double
可能會隱藏問題,所以我認為最好從String
表示開始工作。 如果 String 表示數字,則轉換為BigDecimal
是准確的。 從float
或double
到BigDecimal
轉換也是如此。 以下是精確表示為float
或double
測試函數:
public static boolean isExactDouble(String data) {
BigDecimal rawBD = new BigDecimal(data);
double d = rawBD.doubleValue();
BigDecimal cookedBD = new BigDecimal(d);
return cookedBD.compareTo(rawBD) == 0;
}
public static boolean isExactFloat(String data) {
BigDecimal rawBD = new BigDecimal(data);
float d = rawBD.floatValue();
BigDecimal cookedBD = new BigDecimal(d);
return cookedBD.compareTo(rawBD) == 0;
}
我想在這里分享這個功能。
// Determine whether number is exactly representable in double.
// i.e., No rounding to an approximation during the conversion.
// Results are valid for numbers in the range [2^-24, 2^52].
public static boolean isExactFloat(double val) {
int exp2 = Math.getExponent(val);
int exp10 = (int) Math.floor(Math.log10(Math.abs(val)));
// check for any mismatch between the exact decimal and
// the round-trip representation.
int rightmost_bits = (52 - exp2) - (16 - exp10);
// create bitmask for rightmost bits
long mask = (1L << rightmost_bits) - 1;
// test if all rightmost bits are 0's (i.e., no rounding)
return (Double.doubleToLongBits(val) & mask) == 0;
}
編輯:上述功能可以更短
public static boolean isExactFloat(double val) {
int exp2 = Math.getExponent(val);
int exp10 = (int) Math.floor(Math.log10(Math.abs(val)));
long bits = Double.doubleToLongBits(val);
// test if at least n rightmost bits are 0's (i.e., no rounding)
return Long.numberOfTrailingZeros(bits) >= 36 - exp2 + exp10;
}
不清楚您的問題是否與精度(准確表示 0.24)或重復數字(如 1 / 3.0)有關。
一般來說,如果您使用傳統的浮點表示,精度問題總是會出現。
如果精度對您來說是一個真正的問題,您應該考慮使用BigDecimal 。 雖然不像double
那樣靈活,但它還有其他優點,如任意精度,並且您還可以控制非精確計算中的舍入行為(如重復十進制值)。
如果您所追求的只是精度控制,您可能需要查看 Apache Commons Math Precision類。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.