8

What exactly is the reason behind this peculiar interface of nextafter (and nexttoward) functions? We specify the direction by specifying the value we want to move toward.

At the first sight it feels as if something non-obvious is hidden behind this idea. In my (naive) opinion the first choice for such functions would be something like a pair of single-parameter functions nextafter(a)/nextbefore(a). The next choice would be a two-parameter function nextafter(a, dir) in which the direction dir is specified explicitly (-1 and +1, some standard enum, etc.).

But instead we have to specify a value we want to move toward. Hence a number of questions

  1. (A vague one). There might be some clever idea or idiomatic pattern that is so valuable that it influenced this choice of interface in these standard functions. Is there?

  2. What if decide to just blindly use -DBL_MAX and +DBL_MAX as the second argument for nextafter to specify the negative and positive direction respectively. Are there any pitfalls in doing so?

  3. (A refinement of 2). If I know for sure that b is [slightly] greater than a, is there any reason to prefer nextafter(a, b) over nextafter(a, DBL_MAX)? E.g. is there a chance of better performance for nextafter(a, b) version?

  4. Is nextafter generally a heavy operation? I know that it is implementation-dependent. But, again, assuming an implementation that is based in IEEE 754 representations, is it fairly "difficult" to find the adjacent floating-point value?

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    I can tell you that IEEE 754-2008 (as incorporated into C by ISO/IEC TS 18661) adds exactly the single-parameter functions you suggest would be ideal, under the names `nextup` and `nextdown`. I can also tell you that the [glibc implementation of `nextafter`](https://sourceware.org/git/?p=glibc.git;a=blob;f=math/s_nextafter.c;hb=HEAD) is not doing anything clever, and that it only cares about whether its second argument is greater or less than its first argument, not the magnitude of the difference. I don't know why `nextafter` was specified the way it was in the first place. – zwol Aug 28 '18 at 17:36
  • I also don't know how expensive this operation is. The glibc implementation does bit-twiddling on the binary representation using integer instructions, which is often fairly heavyweight just because you have to copy from float to integer registers and back, but that's just a guess. Also, there may not be any better way to do this, short of adding it as a primitive operation to the CPU's instruction set. – zwol Aug 28 '18 at 17:42
  • 3
    `nextafter( double from, double to )` always has a well defined answer. `AnT_nextafter(a)` does not on a system without a code-able infinity. Example: `AnT_nextafter(DBL_MAX)`? Still a good question. This corner case if the only advantage I see to `nextafter(from, to)`. – chux - Reinstate Monica Aug 28 '18 at 18:41

1 Answers1

5

With IEEE-754 binary floating point representations, if both arguments of nextafter are finite and the two arguments are not equal, then the result can be computed by either adding one to or subtracting one from the representation of the number reinterpreted as an unsigned integer [Note 1]. The (slight) complexity results from correctly dealing with the corner cases which do not meet those preconditions, but in general you'll find that it is extremely fast.

Aside from NaNs, the only thing that matters about the second argument is whether it is greater than, less than, or equal to the first argument.

The interface basically provides additional clarity for the corner case results, but it is also sometimes useful. In particular, the usage nextafter(x, 0), which truncates regardless of sign, is often convenient. You can also take advantage of the fact that nextafter(x, x); is x to clamp the result at an arbitrary value.

The difference between nextafter and nexttowards is that the latter allows you to use the larger dynamic range of long double; again, that helps with certain corner cases.


  1. Strictly speaking, if the first argument is a zero of some sign and the other argument is a valid non-zero number of the opposite sign, then the argument needs to have its sign bit flipped before the increment. But it seemed too much legalese to add that to the list, and it is still hardly a complicated transform.
rici
  • 234,347
  • 28
  • 237
  • 341
  • To be clear about the footnote: If you have -0.0, the value representation is `0x80000000 00000000`. Just adding 1 to the representation will yield `0x80000000 00000001`, which is `-FLT_MIN`, whereas the correct value is `0x00000000 00000001`. So in this particular corner case, you need to flip the sign bit before adding the one. If the words were confusing, please let me know so I can try to write it better. – rici Aug 28 '18 at 21:13