两个值之间唯一的“double”数
Number of Unique `double`s between Two Values
我在下面提供了这个问题的答案(我没有找到 'answer own question' 按钮)
原问题
在Java中,我们有浮点类型double
,它在内存中被编码为64位。由此我们知道它最多可以取 2^64
个可能的值,减去一些特殊值。我们可以一一列举。
给定一个 double
值 d
,例程 Math.nextUp(d)
和 Math.nextDown(d)
将分别计算下一个更大和更小的值 double
。现在我想知道如何计算从 double
a
到 b
的 double
步数,即我的方法 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
的递增和递减,即,我可以 运行 遇到指数问题吗?我强烈假设后一种情况,想知道实现我的目标的正确方法是什么?
再次:我希望它适用于任意 double
s,即 difference(1e-3,3.442e201)
应该 return 我需要从 1e-3
到 3.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
都是加一,所以减一的结果应该和步数一致。
我在下面提供了这个问题的答案(我没有找到 'answer own question' 按钮)
原问题
在Java中,我们有浮点类型double
,它在内存中被编码为64位。由此我们知道它最多可以取 2^64
个可能的值,减去一些特殊值。我们可以一一列举。
给定一个 double
值 d
,例程 Math.nextUp(d)
和 Math.nextDown(d)
将分别计算下一个更大和更小的值 double
。现在我想知道如何计算从 double
a
到 b
的 double
步数,即我的方法 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
的递增和递减,即,我可以 运行 遇到指数问题吗?我强烈假设后一种情况,想知道实现我的目标的正确方法是什么?
再次:我希望它适用于任意 double
s,即 difference(1e-3,3.442e201)
应该 return 我需要从 1e-3
到 3.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
都是加一,所以减一的结果应该和步数一致。