10

在 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”。

我对超过 3 位小数 (#.###) 的精度不感兴趣。我该如何处理这个问题来乘以浮点数并可靠地比较它们?

4

9 回答 9

19

浮点数永远不应该像(a==b)(Math.abs(a-b) < delta)那样进行比较,而是像delta是一个小数一样。

十进制形式具有固定位数的浮点值不一定具有二进制形式的固定位数。

为清楚起见添加:

虽然==浮点数的严格比较几乎没有实际意义,但严格<>比较相反,是一个有效的用例(示例 - 当某个值超过阈值时逻辑触发(val > threshold) && panic();:)

于 2010-05-24T09:51:47.947 回答
7

如果您对固定精度数字感兴趣,则应该使用固定精度类型BigDecimal,而不是本质上近似(尽管精度很高)的类型float。Stack Overflow 上有许多类似的问题,它们更详细地讨论了这一点,涉及多种语言。

于 2010-05-24T09:43:50.977 回答
6

我认为它与 Java 无关,它发生在任何 IEEE 754 浮点数上。这是因为浮点表示的性质。任何使用 IEEE 754 格式的语言都会遇到同样的问题。

正如上面大卫所建议的,您应该使用 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");
    }
}
于 2010-05-24T10:11:54.170 回答
3

我在单元测试中使用这段代码来比较两个不同计算的结果是否相同,除非浮点数学错误。

它通过查看浮点数的二进制表示来工作。大多数复杂性是由于浮点数的符号不是二进制补码这一事实。在对此进行补偿之后,它基本上归结为一个简单的减法来获得 ULP 的差异(在下面的评论中解释)。

/**
 * 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;
}
于 2013-07-26T01:20:33.233 回答
2

这是所有浮点表示的一个弱点,它的发生是因为一些在十进制系统中看起来有固定位数的小数,实际上在二进制系统中有无限个小数。所以你认为的 1.2 实际上类似于 1.199999999997 因为当用二进制表示它时,它必须在某个数字后去掉小数,你会失去一些精度。然后将它乘以 3 实际上得到 3.5999999...

http://docs.python.org/py3k/tutorial/floatingpoint.html <-这可能会更好地解释它(即使它是用于python,它也是浮点表示的常见问题)

于 2010-05-24T10:14:05.397 回答
2

就像其他人写道:

将浮点数与: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");
}
于 2010-05-24T10:46:57.407 回答
2

有一个用于比较双打的 apache 类:org.apache.commons.math3.util.Precision

它包含一些有趣的常数:SAFE_MINEPSILON,它们是执行算术运算时可能出现的最大偏差。

它还提供了比较、相等或舍入双精度数的必要方法。

于 2015-05-19T11:24:31.363 回答
0

四舍五入是个坏主意。根据需要使用BigDecimal并设置它的精度。喜欢:

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
}
于 2019-11-20T13:21:09.553 回答
-1

要比较两个浮点数,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 位小数并正确舍入最后一位。

于 2010-05-24T10:12:38.363 回答