Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get next smallest nearest number to a decimal

Introduction

For some calculations I need to find the smallest possible number I can add/subtract from a specified number without JavaScript getting in trouble with the internal used data type.

Goal

I tried to write a function which is able to return the next nearest number to VALUE in the direction of value DIR.

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;
    }
}

The problem with this is, that JavaScript uses a 64-bit float type (I think) which has different minimum step sizes depending on its current exponent.

Problem in detail

The problem is the step size depending on its current exponent:

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)

Summary

So how can I find the smallest possible number I can add/subtract from a value specified in a parameter in any direction? In C++ this would be easily possible by accessing the numbers binary values.

like image 815
Benjamin Schulte Avatar asked Dec 26 '14 16:12

Benjamin Schulte


People also ask

How do you round decimals to the nearest whole number?

To round a number to the nearest whole number, you have to look at the first digit after the decimal point. If this digit is less than 5 (1, 2, 3, 4) we don't have to do anything, but if the digit is 5 or greater (5, 6, 7, 8, 9) we must round up.


1 Answers

I tried to implement Pointy's suggestion from the comments (using typed arrays). This is loosely adapted from glibc's implementation of nextafter. Should be good enough.

You can actually just increment/decrement the 64-bit integer representation of a double to get the wanted result. A mantissa overflow will overflow to the exponent which happens to be just what you want.

Since JavaScript doesn't provide a Uint64Array I had to implement a manual overflow over two 32-bit integers.

This works on little-endian architectures, but I've left out big-endian since I have no way to test it. If you need this to work on big-endian architectures you'll have to adapt this code.

// 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>");
like image 139
Lucas Trzesniewski Avatar answered Sep 19 '22 06:09

Lucas Trzesniewski