I want to perform a simple linear interpolation between $A$ and $B$ (which are binary floating-point values) using floating-point math with IEEE-754 round-to-nearest-or-even rounding rules, as accurately as possible. Please note that speed is not a big concern here.
I know of two basic approaches. I'll use the symbols $\oplus, \ominus, \otimes, \oslash$ following Knuth [1], to mean floating-point addition, subtraction, product and division, respectively (actually I don't use division, but I've listed it for completeness).
(1) $\quad f(t) = A\,\oplus\,(B\ominus A)\otimes t$
(2) $\quad f(t) = A\otimes(1\ominus t)\,\oplus \,B\otimes t$
Each method has its pros and cons. Method (1) is clearly monotonic, which is a very interesting property, while it is not obvious at all to me that that holds for method (2), and I suspect it may not be the case. On the other hand, method (2) has the advantage that when $t = 1$ the result is exactly $B$, not an approximation, and that is also a desirable property (and exactly $A$ when $t = 0$, but method (1) does that too). That follows from the properties listed in [2], in particular:
$u\oplus v = v\oplus u$
$u\ominus v = u\oplus -v$
$u\oplus v = 0$ if and only if $v = -u$
$u\oplus 0 = u$
$u\otimes 1 = u$
$u\otimes v = 0$ if and only if $u = 0$ or $v = 0$
In [3] Knuth also discusses this case:
$u' = (u\oplus v)\ominus v$
which implicitly means that $u'$ may or may not be equal to $u$. Replacing $u$ with $B$ and $v$ with $-A$ and using the above rules, it follows that it's not granted that $A\oplus(B\ominus A) = B$, meaning that method (1) does not always produce $B$ when $t = 1$.
So, here come my questions:
- Is method (2) guaranteed to be monotonic?
- If not, is there a better method that is accurate, monotonic and yields $A$ when $t = 0$ and $B$ when $t = 1$?
If not (or you don't know), does method (1) when $t = 1$ always overshoot (that is, $A\oplus(B\ominus A)=A+(B-A)\cdot t$ for some $t \geq 1$)? Always undershoot (ditto for some $t \leq 1$)? Or sometimes overshoot and sometimes undershoot?
I assume that if method (1) always undershoots, I can make a special case when $t = 1$ to obtain the desired property of being exactly equal to $B$ when $t = 1$, but if it always overshoots, then I can't. That's the reason for question 3.
EDIT: I've found that the answer to question 3 is that it sometimes overshoots and sometimes undershoots. For example, in double precision:
-0x1.cae164da859c9p-1 + (0x1.eb4bf7b6b2d6ep-1 - (-0x1.cae164da859c9p-1))
results in 0x1.eb4bf7b6b2d6fp-1
, which is 1 ulp greater than the original, while
-0x1.be03888ad585cp-1 + (0x1.0d9940702d541p-1 - (-0x1.be03888ad585cp-1))
results in 0x1.0d9940702d540p-1
, which is 1 ulp less than the original. On the other hand, the method that I planned (special casing $t=1$) won't fly, because I've found it can be the case where $t < 1$ and $A\oplus(B\ominus A)\otimes t > B$, for example:
t = 0x1.fffffffffffffp-1
A = 0x1.afb669777cbfdp+2
B = 0x1.bd7b786d2fd28p+1
$A \oplus (B \ominus A)\otimes t =\,$ 0x1.bd7b786d2fd29p+1
which means that if method (1) is to be used, the only strategy that may work is clamping.
Update: As noted by Davis Herring in a comment and later checked by me, special casing t=1 actually works.
References
[1] D.E.Knuth, The Art of Computer Programming, vol. 2: Seminumerical algorithms, third edition, p. 215
[2] Op. cit. pp. 230-231
[3] Op. cit. p.235 eq.(41)
t
as volatile fixed it. I've updated the program. $\endgroup$