简体   繁体   中英

How to get closest floating point number that is less/greater than a given number

Here is a sample function:

function step(x, min, max) {
  return x >= min && x <= max ? x : 0;
}

console.log(step(-3 - Number.EPSILON, -3, 5)); // Expected 0, actual -3
console.log(step(5 + Number.EPSILON, -3, 5)); // Expected 0, actual 5

I need to check, that it returns zero for values outside [min, max] interval. For sure I can subtract/add a bigger number, for example 1. But I'm pretty sure, that there should exist a function returning previous/next floating point number. Could you please suggest the function or how to implement it?

Not all adjacent representable numbers are the same mathematical distance from one another. Floating point arcana isn't my strong suit, but if you want to find the next representable number, I think you need to keep increasing what you add/subtract from it by Number.EPSILON for as long as you keep getting the same number.

The very naive, simplistic approach would look like this (but keep reading):

 // DON'T USE THIS function next(x) { const ep = x < 0 ? -Number.EPSILON : Number.EPSILON; let adder = ep; let result; do { result = x + adder; adder += ep; } while (result === x); return result; } console.log(`Next for -3: ${next(-3)}`); console.log(`Next for 5: ${next(5)}`);

(That's assuming direction based on the sign of the number given, which is probably not what you really want, but is easily switched up.)

But , that would take hours (at least) to handle next(Number.MAX_SAFE_INTEGER) .

When I posted my caveat on the above originally, I said a better approach would take the magnitude of x into account "...or do bit twiddling (which definitely takes us into floating point arcana land)..." and you pointed to Java's Math.nextAfter operation, so I had to find out what they do. And indeed, it's bit twiddling, and it's wonderfully simple. Here's a re-implementation of the OpenJDK's version from here (the line number in that link will rot):

 // A JavaScript implementation of OpenJDK's `Double.nextAfter` method. function nextAfter(start, direction) { // These arrays share their underlying memory, letting us use them to do what // Java's `Double.doubleToRawLongBits` and `Double.longBitsToDouble` do. const f64 = new Float64Array(1); const b64 = new BigInt64Array(f64.buffer); // Comments from https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Math.java: /* * The cases: * * nextAfter(+infinity, 0) == MAX_VALUE * nextAfter(+infinity, +infinity) == +infinity * nextAfter(-infinity, 0) == -MAX_VALUE * nextAfter(-infinity, -infinity) == -infinity * * are naturally handled without any additional testing */ /* * IEEE 754 floating-point numbers are lexicographically * ordered if treated as signed-magnitude integers. * Since Java's integers are two's complement, * incrementing the two's complement representation of a * logically negative floating-point value *decrements* * the signed-magnitude representation. Therefore, when * the integer representation of a floating-point value * is negative, the adjustment to the representation is in * the opposite direction from what would initially be expected. */ // Branch to descending case first as it is more costly than ascending // case due to start != 0.0d conditional. if (start > direction) { // descending if (start !== 0) { f64[0] = start; const transducer = b64[0]; b64[0] = transducer + (transducer > 0n ? -1n : 1n); return f64[0]; } else { // start == 0.0d && direction < 0.0d return -Number.MIN_VALUE; } } else if (start < direction) { // ascending // Add +0.0 to get rid of a -0.0 (+0.0 + -0.0 => +0.0) // then bitwise convert start to integer. f64[0] = start + 0; const transducer = b64[0]; b64[0] = transducer + (transducer >= 0n ? 1n : -1n); return f64[0]; } else if (start == direction) { return direction; } else { // isNaN(start) || isNaN(direction) return start + direction; } } function test(start, direction) { const result = nextAfter(start, direction); console.log(`${start} ${direction > 0 ? "up" : "down"} is ${result}`); } test(-3, -Infinity); test(5, Infinity); test(Number.MAX_SAFE_INTEGER, Infinity); test(Number.MAX_SAFE_INTEGER + 2, Infinity);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM