【问题标题】:Manipulating and comparing floating points in java在java中操作和比较浮点数
【发布时间】:2011-02-23 04:07:54
【问题描述】:

在 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 位小数 (#.###) 的精度不感兴趣。我该如何处理这个问题来乘以浮点数并可靠地比较它们?

【问题讨论】:

  • 声明浮点数如:float a = 1.2f; 和双精度数如 double d = 1.2d; 同样在你的 if 语句中:if(c == 3.6f)
  • 除了@bobah 的回答,我建议查看Math.ulp() 函数。
  • 使用BigDecimal 进行浮动和双重操作。见link

标签: java floating-point floating-accuracy


【解决方案1】:

四舍五入是个坏主意。使用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
}

【讨论】:

    【解决方案2】:

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

    它通过查看浮点数的二进制表示来工作。大多数复杂性是由于浮点数的符号不是二进制补码这一事实。在对此进行补偿之后,它基本上可以归结为一个简单的减法来获得 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;
    }
    

    【讨论】:

    • 您也可以考虑使用Float.floatToRawIntBits(),在方法的开头检查NaN。事实上,floatToIntBits() 只检查NaN 的结果,将其替换为预定义的整数值0x7fc00000。这样做的主要原因是floatToIntBits() 实际上调用了floatToRawIntBits(),使其执行速度变慢。另一种方法是检查0x7fc00000 的转换位,但您不需要两个检查。
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

      一般规则是浮点数永远不应该像 (a==b) 那样比较,而应该像 (Math.abs(a-b) &lt; delta) 那样比较,其中 delta 是一个小数。

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

      为清楚起见添加:

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

      【讨论】:

      • 建议使用容差进行比较是不恰当的建议,因为它减少了不平等的虚假报告,但代价是增加了平等的虚假报告,而且您不知道这对于您一无所知的应用程序是否可以接受。应用程序可能对寻求不平等而不是寻求平等“更感兴趣”,或者可能需要满足其他规范。
      • @Eric - 处理浮点数时没有恒等或不等的概念,只有距离的概念。如果在我在答案中给出的公式中,您将&lt; 替换为&gt;,您将获得一个比较浮点数在距离方面的不公平性的标准。对于大多数实际应用来说,浮点数在计算机内存中的按位表示并不重要
      • 您正在检查一个阻尼振荡器,并希望区分欠阻尼、过阻尼和临界阻尼。这需要严格的测试,不能容忍。允许公差将导致取负数的平方根。然而,尽管有这个例子,你的请求是一个稻草人。建议不要与公差进行比较并不意味着比较完全相等,因为还有其他选择。例如,一种可能性是完全避免使用比较;只报告可用的最佳结果,而不是试图将其强制为量化结果。
      • 不管有什么例子,建议人们使用容差进行比较存在基本问题。它增加了关于平等的虚假报告,并且由于您不了解应用程序,因此您无法知道这是可以接受的还是有问题的。
      • “准确比较” - 是一个没有意义的术语,它无法量化。我想我很了解 IEEE754,我给出的答案准确地回答了该主题的问题,它简洁明了。相反,你的评论太笼统了,几乎是题外话。
      【解决方案5】:

      就像其他人写的:

      比较浮点数:if (Math.abs(a - b) &lt; 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");
      }
      

      【讨论】:

        【解决方案6】:

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

        http://docs.python.org/py3k/tutorial/floatingpoint.html

        【讨论】:

        • +1 - all 有限精度浮点数系统存在这个问题。无论你选择什么基数,有些有理数都不能准确表示。
        【解决方案7】:

        要在#.### 的精度范围内比较两个浮点数,f1f2,我相信您需要这样做:

        ((int) (f1 * 1000 + 0.5)) == ((int) (f2 * 1000 + 0.5))
        

        f1 * 10003.14159265... 提升到 3141.59265+ 0.5 导致 3142.09265(int) 去掉小数点后,3142。也就是说,它包含 3 位小数并正确舍入最后一位。

        【讨论】:

        • 使用 epsilon 比较更好:考虑一下如果 f1 == 3.1414999999999f2 == 3.1415000000001 会发生什么。
        • 该死。我虽然我有它:-) 当然。我同意你的看法。使用 epsilon 进行比较要好得多。但它是否准确地将两个浮点数与其前 3 个小数点进行比较?
        【解决方案8】:

        我认为它与 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");
            }
        }
        

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-05-13
            • 2011-08-20
            • 2017-02-07
            • 2015-01-20
            • 2016-05-24
            • 2016-02-08
            • 2013-03-26
            • 1970-01-01
            相关资源
            最近更新 更多