获得小数的下一个最小的最近数字

时间:2014-12-26 16:51:05

标签: javascript math

简介

对于某些计算,我需要找到可以在指定数字上加/减的最小可能数,而不会因为内部使用的数据类型而遇到麻烦。

目标

我尝试编写一个函数,它能够在值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;
    }
}

这个问题是,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)

摘要

那么我怎样才能找到可以在任何方向上从参数中指定的值加/减的最小可能数?在C ++中,通过访问数字二进制值可以很容易地实现这一点。

4 个答案:

答案 0 :(得分:5)

我试图从评论中使用Pointy的建议(使用类型化数组)。这很容易改编自glibc nextafter的实施。应该足够好了。

can actually just increment/decrement一个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>");

答案 1 :(得分:0)

Number.MIN_VALUE是可表示的最小数字,而不是可表示数字之间可能存在的最小差异。由于javascript处理浮点数的方式,可表示数字之间的最小可能差异随数字的大小而变化。随着数量变大,精度变小。因此,没有一个数字可以解决您的问题。我建议您重新考虑如何解决问题或选择要使用的数字子集,而不是全部MAX和MIN值。

例如:1.7976931348623156e+308 == 1.7976931348623155e+308 //true

此外,通过从MAX_VALUE中减去MIN_VALUE,您尝试获取javascript以准确表示超过600个有效数字的数字。这有点太多了。

答案 2 :(得分:0)

查找不同浮点数的(最小)增量,因为它可能随浮点精度而有所不同!

以下用于设置输入类型=数字的step属性:

function getIncrement(number) {
  function getDecimals(number) {
    let d = parseFloat(number).toString().split('.')[1]
    if (d) {return d.length}
    return 0
  }
  return (0 + "." + Array(getDecimals(number)-1).fill(0).join("") + "1").toLocaleString('fullwide', {useGrouping:false})
}

console.log(
  getIncrement('0.000047'),
  getIncrement('0.000054547'),
  getIncrement('0.004')
)


function createInput(value){
  let p = document.createElement("input")
  p.type = "number"
  p.step = getIncrement(value)
  p.min = 0
  p.max = 100
  p.value = value
  panel.appendChild(p)
}

createInput(0.00000105)
<div id="panel"></div>

答案 3 :(得分:0)

这是使用Math.log2()的一种快速而肮脏的方法,它不需要堆分配并且是much faster

function getNext(num) {
    return num + 2 ** (Math.log2(num) - 52);
}

function getPrevious(num) {
    return num - 2 ** (Math.log2(num) - 52);
}

缺点是它并不完美,只是非常接近。结果对于非常小的值来说效果不佳。 (num < 2 ** -1000

数字> 9.332636185032189e-302可能会失败,但是我还没有找到。小于数字的情况有一些失败案例,例如:

console.log(getNext(Number.MIN_VALUE) === Number.MIN_VALUE); // true

但是,以下方法可以正常工作:

let counter = 0;

// 2 ** -1000 === 9.332636185032189e-302
for (let i = -1000; i < 1000; i += 0.00001) {
    counter++;
    if (getNext(2 ** i) === 2 ** i)
        throw 'err';
    if (getPrevious(2 ** i) === 2 ** i)
        throw 'err';

}

console.log(counter.toLocaleString()); // 200,000,001

但是请注意,“大量测试用例都可以工作”并不是正确性的证明。