0

This is a pretty niche question, but I'm implementing interval arithmetic in JS and I'd like to have correct rounding. Therefore, I need to be able to add two Numbers and have it round towards infinity, -infinity, zero, etc. As far as I can tell JS always rounds towards zero and this behavior isn't changeable, unlike in C/C++ with fesetround.

How can I get around this? I'm willing to have significant performance impacts if it means correct rounding; probably the feature will be toggleable to balance speed and correctness. Perhaps one way to do this would be to somehow make functions roundUp and roundDown which round up/down to the next float value.

As an example of how roundUp/roundDown could be implemented:


const floatStore = new Float64Array(1)

const intView = new Uint32Array(floatStore.buffer)

function roundUp(x) {
  if (x === Infinity)
    return Infinity
  if (x === -Infinity)
    return -Infinity
  if (isNaN(x))
    return NaN
    
  floatStore[0] = x

  let leastSignificantGroup = ++intView[0]

  if (leastSignificantGroup === 0)
    intView[1]++

  return floatStore[0]
}

(5.1).toPrecision(100) // -> 5.0999999999999996447...
roundUp(5.1).toPrecision(100) // -> 5.100000000000000532...
Ovinus Real
  • 528
  • 3
  • 10
  • Lots of floating point questions/answers around like https://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript – charlietfl Jul 19 '20 at 04:05
  • JavaScript uses round-to-nearest, ties-to-even, not toward-zero, per ECMAScript 2019 Language Specification (ECMA-262, 10th edition, June 2019) 6.1.6, second from last paragraph. – Eric Postpischil Jul 19 '20 at 10:06
  • ECMAScript (which JavaScript implements) does not provide facilities for either controlling the rounding mode or easily adjusting a floating-point number to the next or previous representable value. JavaScript and ECMAScript are not designed for this. – Eric Postpischil Jul 19 '20 at 15:55
  • @EricPostpischil Well, I can at least get close with the roundUp and roundDown functions I described, which round to the next/previous float value. It would be generally one float value wider than ideal, but that's fine. I just asked this question wondering if the rounding could be done by manipulating the floats' significands and mantissas. If not, I guess I'll close it. – Ovinus Real Jul 19 '20 at 16:16
  • Another interesting question is what happens when C/C++ code with correct rounding is compiled with Emscripten. I'm guessing WASM also doesn't have rounding modes, so it's probably done another way. – Ovinus Real Jul 19 '20 at 16:17
  • If you're curious, [here's some implementations](https://github.com/SimpleArt/pyroot/blob/python_le_3_7/pyroot/interval/_src/fpu_rounding.py) of various arithmetic operations with different rounding modes, which rely on `math.nextafter` (which can be implemented using your `roundUp` and `roundDown` functions). They give highly precise rounding e.g. `[1, 2] / 3 = [0.3333333333333333, 0.6666666666666667]`. Note that this is `[1 / 3, roundUp(2 / 3)]`. – Simply Beautiful Art Jun 18 '22 at 21:53

1 Answers1

0

For anyone looking for a fast way to get consecutive floats, this works:

const MAGIC_ROUND_C = 1.1113332476497816e-16 // just above machine epsilon / 2
const POSITIVE_NORMAL_MIN = 2.2250738585072014e-308
const POSITIVE_DENORMAL_MIN = Number.MIN_VALUE

function roundUp (x) {
  if (x >= -POSITIVE_NORMAL_MIN && x < POSITIVE_NORMAL_MIN) {
    // denormal numbers
    return x + POSITIVE_DENORMAL_MIN
  } else if (x === -Infinity) {
    // special case
    return -Number.MAX_VALUE
  }

  return x + Math.abs(x) * MAGIC_ROUND_C
}

function roundDown (x) {
  if (x > -POSITIVE_NORMAL_MIN && x <= POSITIVE_NORMAL_MIN) {
    return x - POSITIVE_DENORMAL_MIN
  } else if (x === Infinity) {
    return Number.MAX_VALUE
  }

  return x - Math.abs(x) * MAGIC_ROUND_C
}

The only oddity is that it treats +0 and -0 as the same, so rounding them up both gives the minimum denormal, and same for rounding down.

Ovinus Real
  • 528
  • 3
  • 10