介绍
对于某些计算,我需要找到可以在指定数字中添加/减去的最小可能数,而不会使内部使用的数据类型出现问题.
目标
我试着编写一个函数,它能够在值DIR的方向上将下一个最接近的数字返回到VALUE.
function nextNearest(value, direction) {
// Special cases for value==0 or value==direction removed
if (direction < value) {
return value - Number.MIN_VALUE;
} else {
return value + Number.MIN_VALUE;
}
}
Run Code Online (Sandbox Code Playgroud)
这个问题是,JavaScript使用64位浮点类型(我认为),它具有不同的最小步长,具体取决于它的当前指数.
问题详细
问题是步长取决于其当前指数:
var a = Number.MIN_VALUE;
console.log(a);
// 5e-324
console.log(a + Number.MIN_VALUE);
// 1e-323 (changed, as expected)
var a = Number.MAX_VALUE;
console.log(a);
// 1.7976931348623157e+308
console.log(a - Number.MIN_VALUE);
// 1.7976931348623157e+308 (that's wrong)
console.log(a - Number.MIN_VALUE == a);
// true (which also is wrong)
Run Code Online (Sandbox Code Playgroud)
摘要
那么我怎样才能找到可以在任何方向上从参数中指定的值加/减的最小可能数?在C++中,通过访问数字二进制值可以很容易地实现这一点.
我试图从评论中使用Pointy的建议(使用类型化数组).这很容易改编自glibc的实现nextafter
.应该够好了.
实际上,您只需递增/递减 double的64位整数表示即可获得所需结果.尾数溢出会溢出到指数,恰好就是你想要的.
由于JavaScript没有提供,Uint64Array
我不得不在两个32位整数上实现手动溢出.
这适用于little-endian架构,但由于我无法测试它,所以我遗漏了big-endian.如果您需要在大端架构上工作,则必须调整此代码.
// Return the next representable double from value towards direction
function nextNearest(value, direction) {
if (typeof value != "number" || typeof direction != "number")
return NaN;
if (isNaN(value) || isNaN(direction))
return NaN;
if (!isFinite(value))
return value;
if (value === direction)
return value;
var buffer = new ArrayBuffer(8);
var f64 = new Float64Array(buffer);
var u32 = new Uint32Array(buffer);
f64[0] = value;
if (value === 0) {
u32[0] = 1;
u32[1] = direction < 0 ? 1 << 31 : 0;
} else if ((value > 0) && (value < direction) || (value < 0) && (value > direction)) {
if (u32[0]++ === 0xFFFFFFFF)
u32[1]++;
} else {
if (u32[0]-- === 0)
u32[1]--;
}
return f64[0];
}
var testCases = [0, 1, -1, 0.1,
-1, 10, 42e42,
0.9999999999999999, 1.0000000000000002,
10.00000762939453, // overflows between dwords
5e-324, -5e-324, // minimum subnormals (around zero)
Number.MAX_VALUE, -Number.MAX_VALUE,
Infinity, -Infinity, NaN];
document.write("<table><tr><th>n</th><th>next</th><th>prev</th></tr>");
testCases.forEach(function(n) {
var next = nextNearest(n, Infinity);
var prev = nextNearest(n, -Infinity);
document.write("<tr><td>" + n + "</td><td>" + next + "</td><td>" + prev + "</td></tr>");
});
document.write("</table>");
Run Code Online (Sandbox Code Playgroud)