java - 两个值之间唯一 `double` 的数量

标签 java floating-point precision numerical

我在下面提供了这个问题的答案(我没有找到“回答自己的问题”按钮)

原始问题

在 Java 中,我们有浮点类型 double,它在内存中被编码为 64 位。由此我们知道它最多可以取 2^64 可能的值,减去一些特殊值。我们可以一一列举。

例程 Math.nextUp(d)Math.nextDown(d),给定一个 doubled,将分别计算下一个更大和更小的 double。现在我想知道如何计算从一个 double abdouble 步数,即我的方法 difference(a,b) 应该按如下方式工作:

assume a fixed, given a.

            b = ....               | difference(a, b)
===========================================================
   a                               |  0
   Math.nextUp(a)                  |  1
   Math.nextDown(a)                |  1
   Math.nextUp(Math.nextUp(a))     |  2
   Math.nextDown(Math.nextDown(a)) |  2

...等等。

在Java OpenJDK中,这两个方法的实现如下:

public static double nextUp(double d) {
  if( Double.isNaN(d) || d == Double.POSITIVE_INFINITY)
    return d;
  else {
    d += 0.0d;
    return Double.longBitsToDouble(Double.doubleToRawLongBits(d) +
                                    ((d >= 0.0d)?+1L:-1L));
  }
}

public static double nextDown(double d) {
  if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY)
    return d;
  else {
    if (d == 0.0)
      return -Double.MIN_VALUE;
    else
      return Double.longBitsToDouble(Double.doubleToRawLongBits(d) +
                                     ((d > 0.0d)?-1L:+1L));
  }
}

我可以安全地做类似的事情吗,或者这只是因为他们只考虑 1 的递增和递减才有效,也就是说,我可能会遇到指数问题吗?我强烈假设后一种情况,想知道实现我的目标的正确方法是什么?

同样:我希望它适用于任意 double,即 difference(1e-3,3.442e201) 应该返回数字 Math.nextUp 我需要从 1e-33.44e201 的步骤。显然,在这种情况下,仅迭代和计数 Math.nextUp 是行不通的。

非常感谢, 托马斯。

问题的答案

感谢@Thilo 的评论,事实证明计算差异实际上很容易。好吧,至少看起来是那么简单。

Java代码如下:

/** Some mathematical utilities */
public final class MathUtils {

  /**
   * The number of unique {@code double} values between {@code a} and
   * {@code b}.
   * 
   * @param a
   *          the first {@code double}
   * @param b
   *          the second {@code double}
   * @return the steps between them, or {@code -1} if either value is
   *         {@link Double#NaN} or both are infinities of different signs
   */
  public static final long difference(final double a, final double b) {
    final long bitsA;
    double useA, useB, temp;

    if ((a != a) || (b != b)) { // take are of NaN
      return -1L;
    }
    useA = (a + 0d);
    useB = (b + 0d);
    if (useA > useB) {
      temp = useB;
      useB = useA;
      useA = temp;
    }
    if (useA == useB) {
      return 0L;
    }
    if (useA <= Double.NEGATIVE_INFINITY) {
      return -1L;
    }
    if (useB >= Double.POSITIVE_INFINITY) {
      return -1L;
    }

    if (useA < 0d) {
      bitsA = Double.doubleToRawLongBits(-useA);
      if (useB < 0d) {
        return (bitsA - Double.doubleToRawLongBits(-useB));
      }
      return (bitsA + Double.doubleToRawLongBits(useB));
    }
    return (Double.doubleToRawLongBits(useB)
        - Double.doubleToRawLongBits(useA));
  }
}

这里有一些基本的 JUnit 测试来确认结果是否符合预期:

import java.util.Random;

import org.junit.Assert;
import org.junit.Test;


/**
 * A test for math utils
 */
public class MathUtilsTest {

  /** the constructor */
  public MathUtilsTest() {
    super();
  }

  /** test step difference between two values */
  @Test(timeout = 3600000)
  public void testDifferenceBetweenTwoValues() {
    final Random random;
    double start, end;
    int starts, iteration;

    random = new Random();

    for (starts = 333; (--starts) >= 0;) {
      end = start = -(1d / Math.log(1d - random.nextDouble()));
      for (iteration = 0; iteration < 3333; iteration++) {
        Assert.assertEquals(iteration, MathUtils.difference(start, end));
        Assert.assertEquals(iteration, MathUtils.difference(end, start));
        end = Math.nextUp(end);
      }
    }
  }

  /**
   * test the "step" difference of two values, one of which is negative,
   * the other one being positive
   */
  @Test(timeout = 3600000)
  public void testDifferenceBetweenTwoValuesOfDifferentSign() {
    double start, end;
    int iteration;

    end = start = 0d;
    for (iteration = 0; iteration < 333333; iteration++) {
      Assert.assertEquals(
          (MathUtils.difference(start, 0d) + //
              MathUtils.difference(0d, end)),
          MathUtils.difference(start, end));
      Assert.assertEquals(
          (MathUtils.difference(start, 0d) + //
              MathUtils.difference(0d, end)),
          MathUtils.difference(end, start));
      start = Math.nextAfter(start, Double.NEGATIVE_INFINITY);
      end = Math.nextUp(end);
    }
  }

  /** test the border cases of the step difference */
  @Test(timeout = 3600000)
  public void testDifferenceBetweenTwoValuesBorderCases() {
    Assert.assertEquals(0L, MathUtils.difference(0d, 0d));
    Assert.assertEquals(0L, MathUtils.difference(0d, -0d));
    Assert.assertEquals(0L, MathUtils.difference(-0d, 0d));
    Assert.assertEquals(0L, MathUtils.difference(-0d, -0d));

    Assert.assertEquals(1L, MathUtils.difference(0d, Double.MIN_VALUE));
    Assert.assertEquals(1L, MathUtils.difference(Double.MIN_VALUE, 0d));
    Assert.assertEquals(1L, MathUtils.difference(-0d, Double.MIN_VALUE));
    Assert.assertEquals(1L, MathUtils.difference(Double.MIN_VALUE, -0d));

    Assert.assertEquals(1L, MathUtils.difference(0d, -Double.MIN_VALUE));
    Assert.assertEquals(1L, MathUtils.difference(-Double.MIN_VALUE, 0d));
    Assert.assertEquals(1L, MathUtils.difference(-0d, -Double.MIN_VALUE));
    Assert.assertEquals(1L, MathUtils.difference(-Double.MIN_VALUE, -0d));

    Assert.assertEquals(2L,
        MathUtils.difference(Double.MIN_VALUE, -Double.MIN_VALUE));
    Assert.assertEquals(2L,
        MathUtils.difference(-Double.MIN_VALUE, Double.MIN_VALUE));

    Assert.assertEquals((1L << 52L),
        MathUtils.difference(0d, Double.MIN_NORMAL));
    Assert.assertEquals((1L << 52L),
        MathUtils.difference(Double.MIN_NORMAL, 0d));
    Assert.assertEquals((1L << 52L),
        MathUtils.difference(-0d, Double.MIN_NORMAL));
    Assert.assertEquals((1L << 52L),
        MathUtils.difference(Double.MIN_NORMAL, -0d));

    Assert.assertEquals((1L << 52L),
        MathUtils.difference(0d, -Double.MIN_NORMAL));
    Assert.assertEquals((1L << 52L),
        MathUtils.difference(-Double.MIN_NORMAL, 0d));
    Assert.assertEquals((1L << 52L),
        MathUtils.difference(-0d, -Double.MIN_NORMAL));
    Assert.assertEquals((1L << 52L),
        MathUtils.difference(-Double.MIN_NORMAL, -0d));

    Assert.assertEquals((2L << 52L),
        MathUtils.difference(Double.MIN_NORMAL, -Double.MIN_NORMAL));
    Assert.assertEquals((2L << 52L),
        MathUtils.difference(-Double.MIN_NORMAL, Double.MIN_NORMAL));

    Assert.assertEquals(0L, MathUtils.difference(Double.POSITIVE_INFINITY,
        Double.POSITIVE_INFINITY));
    Assert.assertEquals(0L, MathUtils.difference(Double.NEGATIVE_INFINITY,
        Double.NEGATIVE_INFINITY));
    Assert.assertEquals(-1L, MathUtils.difference(Double.POSITIVE_INFINITY,
        Double.NEGATIVE_INFINITY));
    Assert.assertEquals(-1L, MathUtils.difference(Double.NEGATIVE_INFINITY,
        Double.POSITIVE_INFINITY));

    Assert.assertEquals(-1L, MathUtils.difference(Double.NaN, Double.NaN));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.POSITIVE_INFINITY, Double.NaN));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.NEGATIVE_INFINITY, Double.NaN));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.NaN, Double.POSITIVE_INFINITY));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.NaN, Double.NEGATIVE_INFINITY));

    Assert.assertEquals(-1L,
        MathUtils.difference(0d, Double.NEGATIVE_INFINITY));
    Assert.assertEquals(-1L,
        MathUtils.difference(0d, Double.POSITIVE_INFINITY));
    Assert.assertEquals(-1L, MathUtils.difference(0d, Double.NaN));

    Assert.assertEquals(-1L,
        MathUtils.difference(Double.POSITIVE_INFINITY, 0d));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.NEGATIVE_INFINITY, 0d));
    Assert.assertEquals(-1L, MathUtils.difference(Double.NaN, 0d));

    Assert.assertEquals(-1L,
        MathUtils.difference(1d, Double.NEGATIVE_INFINITY));
    Assert.assertEquals(-1L,
        MathUtils.difference(1d, Double.POSITIVE_INFINITY));
    Assert.assertEquals(-1L, MathUtils.difference(1d, Double.NaN));

    Assert.assertEquals(-1L,
        MathUtils.difference(Double.POSITIVE_INFINITY, 1d));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.NEGATIVE_INFINITY, 1d));
    Assert.assertEquals(-1L, MathUtils.difference(Double.NaN, 1d));

    Assert.assertEquals(-1L,
        MathUtils.difference(-1d, Double.NEGATIVE_INFINITY));
    Assert.assertEquals(-1L,
        MathUtils.difference(-1d, Double.POSITIVE_INFINITY));
    Assert.assertEquals(-1L, MathUtils.difference(-1d, Double.NaN));

    Assert.assertEquals(-1L,
        MathUtils.difference(Double.POSITIVE_INFINITY, -1d));
    Assert.assertEquals(-1L,
        MathUtils.difference(Double.NEGATIVE_INFINITY, -1d));
    Assert.assertEquals(-1L, MathUtils.difference(Double.NaN, -1d));
  }
}

对于代码尚未涵盖的任何反指示或问题,我将不胜感激。

干杯, 托马斯。

最佳答案

看来您可以只取两个值的 doubleToRawLongBits 并减去它们(加上一些额外的逻辑来处理符号、过零和 Inf/NaN)。

由于nextUp所做的只是加一,所以减法的结果应该与步数一致。

关于java - 两个值之间唯一 `double` 的数量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33557419/

相关文章:

java - 下面的代码段创建了多少个字符串对象

java - 解决bean三个数据源的循环

java - java 中两次测试运行之间的 jprofiler 开销

c++ - 可靠地使用 double 作为 std::map 键

c - frexp(FLT_MAX, ...) 应该返回什么?

java - 我在调用构造函数时出错(我认为)不知道如何修复它

c++ - 将 float 与零进行比较的标准方法是什么?

mysql 精度只有十进制

python - 从 pandas Dataframe 字符串的科学记数号中获取幂数

c++ - 如何避免远距离的Z-fighting?