两个值之间唯一的“double”数

Number of Unique `double`s between Two Values

我在下面提供了这个问题的答案(我没有找到 'answer own question' 按钮)

原问题

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

给定一个 doubled,例程 Math.nextUp(d)Math.nextDown(d) 将分别计算下一个更大和更小的值 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

...等等。

在JavaOpenJDK中,上述两种方法实现如下:

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 的递增和递减,即,我可以 运行 遇到指数问题吗?我强烈假设后一种情况,想知道实现我的目标的正确方法是什么?

再次:我希望它适用于任意 doubles,即 difference(1e-3,3.442e201) 应该 return 我需要从 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都是加一,所以减一的结果应该和步数一致。