在Java中,浮点数的算术运算并不精确。例如,以下Java代码:
float a = 1.2;
float b= 3.0;
float c = a * b;
if(c == 3.6){
System.out.println("c is 3.6");
}
else {
System.out.println("c is not 3.6");
}
输出:"c不是3.6"。
我对小数点后三位以外的精度不感兴趣(#.###)。如何处理浮点数乘法并可靠地进行比较?
在Java中,浮点数的算术运算并不精确。例如,以下Java代码:
float a = 1.2;
float b= 3.0;
float c = a * b;
if(c == 3.6){
System.out.println("c is 3.6");
}
else {
System.out.println("c is not 3.6");
}
输出:"c不是3.6"。
我对小数点后三位以外的精度不感兴趣(#.###)。如何处理浮点数乘法并可靠地进行比较?
通常情况下,浮点数不应该像(a==b)那样进行比较,而应该像(Math.abs(a-b) < delta)
这样进行比较,其中delta是一个很小的数字。
在十进制形式中具有固定位数的浮点值,在二进制形式中不一定具有固定位数。
为了更加明确,需要补充说明:
尽管严格使用==
比较浮点数的意义非常小,但是相反地,严格使用<
和>
比较则是一个有效的用例(例如 - 当特定值超过阈值时触发逻辑:(val > threshold) && panic();
)
>
替换<
,则可以得到一种比较浮点数不平等性的距离标准。对于大多数实际应用程序而言,浮点数在计算机内存中的位表示的身份是无关紧要的。 - bobahBigDecimal
的固定精度类型,而不是本质上是近似的(虽然高精度)类型 float
。在Stack Overflow上有许多类似的问题,涉及多种编程语言,详细阐述了这一点。我认为这与Java无关,它会在任何IEEE 754浮点数上发生。这是由于浮点表示的性质所致。使用IEEE 754格式的任何语言都会遇到同样的问题。
正如David所建议的那样,您应该使用java.lang.Math类的abs方法来获取绝对值(去除正负号)。
您可以阅读此内容:http://en.wikipedia.org/wiki/IEEE_754_revision,同时一本好的数值方法教材也会充分解决这个问题。
public static void main(String[] args) {
float a = 1.2f;
float b = 3.0f;
float c = a * b;
final float PRECISION_LEVEL = 0.001f;
if(Math.abs(c - 3.6f) < PRECISION_LEVEL) {
System.out.println("c is 3.6");
} else {
System.out.println("c is not 3.6");
}
}
/**
* Compare two floating points for equality within a margin of error.
*
* This can be used to compensate for inequality caused by accumulated
* floating point math errors.
*
* The error margin is specified in ULPs (units of least precision).
* A one-ULP difference means there are no representable floats in between.
* E.g. 0f and 1.4e-45f are one ULP apart. So are -6.1340704f and -6.13407f.
* Depending on the number of calculations involved, typically a margin of
* 1-5 ULPs should be enough.
*
* @param expected The expected value.
* @param actual The actual value.
* @param maxUlps The maximum difference in ULPs.
* @return Whether they are equal or not.
*/
public static boolean compareFloatEquals(float expected, float actual, int maxUlps) {
int expectedBits = Float.floatToIntBits(expected) < 0 ? 0x80000000 - Float.floatToIntBits(expected) : Float.floatToIntBits(expected);
int actualBits = Float.floatToIntBits(actual) < 0 ? 0x80000000 - Float.floatToIntBits(actual) : Float.floatToIntBits(actual);
int difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits;
return !Float.isNaN(expected) && !Float.isNaN(actual) && difference <= maxUlps;
}
double
的版本:/**
* Compare two double precision floats for equality within a margin of error.
*
* @param expected The expected value.
* @param actual The actual value.
* @param maxUlps The maximum difference in ULPs.
* @return Whether they are equal or not.
* @see Utils#compareFloatEquals(float, float, int)
*/
public static boolean compareDoubleEquals(double expected, double actual, long maxUlps) {
long expectedBits = Double.doubleToLongBits(expected) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(expected) : Double.doubleToLongBits(expected);
long actualBits = Double.doubleToLongBits(actual) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(actual) : Double.doubleToLongBits(actual);
long difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits;
return !Double.isNaN(expected) && !Double.isNaN(actual) && difference <= maxUlps;
}
Float.floatToRawIntBits()
,在方法开始时检查NaN
。实际上,floatToIntBits()
仅检查结果是否为NaN
,并将其替换为预定义的整数值0x7fc00000
。这样做的主要原因是floatToIntBits()
实际上调用了floatToRawIntBits()
,使其执行速度变慢。另一种方法是检查转换后的位是否为0x7fc00000
,但您不需要进行两个检查。 - Netherwire像其他人写的一样:
使用以下方法比较浮点数:
if (Math.abs(a - b) < delta)
您可以编写一个好用的方法来执行此操作:
public static int compareFloats(float f1, float f2, float delta)
{
if (Math.abs(f1 - f2) < delta)
{
return 0;
} else
{
if (f1 < f2)
{
return -1;
} else {
return 1;
}
}
}
/**
* Uses <code>0.001f</code> for delta.
*/
public static int compareFloats(float f1, float f2)
{
return compareFloats(f1, f2, 0.001f);
}
if (compareFloats(a * b, 3.6f) == 0)
{
System.out.println("They are equal");
}
else
{
System.out.println("They aren't equal");
}
有一个用于比较双精度浮点数的Apache类:org.apache.commons.math3.util.Precision
它包含一些有趣的常量:SAFE_MIN
和EPSILON
,在执行算术运算时它们是可能的最大偏差。
它还提供了必要的方法来比较、相等或舍入双精度浮点数。
public static void main(String... args) {
float a = 1.2f;
float b = 3.0f;
float c = a * b;
BigDecimal a2 = BigDecimal.valueOf(a);
BigDecimal b2 = BigDecimal.valueOf(b);
BigDecimal c2 = a2.multiply(b2);
BigDecimal a3 = a2.setScale(2, RoundingMode.HALF_UP);
BigDecimal b3 = b2.setScale(2, RoundingMode.HALF_UP);
BigDecimal c3 = a3.multiply(b3);
BigDecimal c4 = a3.multiply(b3).setScale(2, RoundingMode.HALF_UP);
System.out.println(c); // 3.6000001
System.out.println(c2); // 3.60000014305114740
System.out.println(c3); // 3.6000
System.out.println(c == 3.6f); // false
System.out.println(Float.compare(c, 3.6f) == 0); // false
System.out.println(c2.compareTo(BigDecimal.valueOf(3.6f)) == 0); // false
System.out.println(c3.compareTo(BigDecimal.valueOf(3.6f)) == 0); // false
System.out.println(c3.compareTo(BigDecimal.valueOf(3.6f).setScale(2, RoundingMode.HALF_UP)) == 0); // true
System.out.println(c3.compareTo(BigDecimal.valueOf(3.6f).setScale(9, RoundingMode.HALF_UP)) == 0); // false
System.out.println(c4.compareTo(BigDecimal.valueOf(3.6f).setScale(2, RoundingMode.HALF_UP)) == 0); // true
}
要比较两个浮点数 f1
和 f2
,精度为 #.###
,我认为你需要这样做:
((int) (f1 * 1000 + 0.5)) == ((int) (f2 * 1000 + 0.5))
f1 * 1000
将 3.14159265...
提升至 3141.59265
,+ 0.5
得到 3142.09265
,然后使用 (int)
去掉小数部分,得到 3142
。也就是说,它包括3个小数位并正确地四舍五入最后一位数字。
f1 == 3.1414999999999
和 f2 == 3.1415000000001
会发生什么。 - Mark Dickinson
float a = 1.2f;
,双精度浮点数使用:double d = 1.2d;
在if语句中也可以这样写:if(c == 3.6f)
。 - Martijn CourteauxMath.ulp()
函数。 - aeracodeBigDecimal
进行浮点数和双精度数的操作。请参考链接。 - Aldis