这两个 lerp 函数有什么区别?

tem*_*ova 9 c math floating-point linear-interpolation lerp

浮点线性插值中,一位用户提出了以下实现lerp

float lerp(float a, float b, float f) 
{
    return (a * (1.0 - f)) + (b * f);
}
Run Code Online (Sandbox Code Playgroud)

另一位用户提出了以下实现lerp

float lerp(float a, float b, float f)
{
    return a + f * (b - a);
}
Run Code Online (Sandbox Code Playgroud)

显然,由于浮点精度损失,后一种实现效果较差。

然后我查看了维基百科,它对前一个实现说:

float lerp(float a, float b, float f) 
{
    return (a * (1.0 - f)) + (b * f);
}
Run Code Online (Sandbox Code Playgroud)

对于后者:

float lerp(float a, float b, float f)
{
    return a + f * (b - a);
}
Run Code Online (Sandbox Code Playgroud)

这给了我几个问题:

  1. 维基百科指出“相同值之间的 Lerping 可能不会产生相同的值”我认为除了浮点精度之外,函数是相同的。不是

    (a * (1.0 - f)) + (b * f)=a + f * (b - a)
    
    Run Code Online (Sandbox Code Playgroud)

    数学恒等式?

    如果不是,什么值会在两个函数中产生不同的结果?

  2. 维基百科上的单调是什么意思?为什么一个实现是单调的,而另一个实现却不是?

  3. 还有其他常见的实现吗lerp

编辑: 如果后一种实现存在浮点不精确问题,同时速度并不快,为什么它还存在?

Eri*_*hil 9

\n
    \n
  1. 维基百科指出“相同值之间的 Lerping 可能不会产生相同的值”我认为除了浮点精度之外,函数是相同的。不是

    \n
    (a * (1.0 - f)) + (b * f)=a + f * (b - a)\n
    Run Code Online (Sandbox Code Playgroud)\n

    数学恒等式?

    \n
  2. \n
\n

如果不是,什么值会在两个函数中产生不同的结果?

\n
\n

a \xe2\x80\xa2 (1.0\xe2\x88\x92 f ) + ( b \xe2\x80\xa2 f ) = a + f \xe2\x80\xa2 ( b \xe2\x88\x92 a ) 是 a数学上相同,但计算的结果a*(1.0-f) + b*f并不总是等于计算的结果a + f*(b-a)

\n

例如,考虑具有十进制基数和尾数三位数字的浮点格式。设a为 123、b为 223、f为 0.124。

\n

然后1.0-f是 0.876。那么a * .876实数算术结果将是 107.748,但由于结果必须四舍五入到三位有效数字,因此会生成 108。对于b * f,我们会有 27.652,但生成了 27.7。那么108 + 27.7会产生 135.7,但会产生 136。

\n

另一边,b-a产生 100。然后f*100产生 12.4。那么a + 12.4会产生 135.4,但会产生 135。

\n

所以左边136和右边135的计算结果不相等。

\n
\n
    \n
  1. 维基百科上的单调是什么意思?
  2. \n
\n
\n

如果更大的参数产生更大的结果,则函数 f( x ) 是严格升序的:x 0 < x 1意味着 f( x 0 ) < f( x 1 )。如果x 0 < x 1意味着 f( x 0 ) \xe2\x89\xa4 f( x 1 ),则它是弱升序。如果x 0 < x 1分别意味着 f( x 0 ) > f( x 1 ) 或 f( x 0 ) \xe2\x89\xa5 f( x 1 ),则它是严格或弱下降的。如果函数严格升序或严格降序,则该函数是严格单调的。如果它是弱上升或弱下降,那么它是弱单调的。

\n

当一个函数被称为单调时,作者的意思是它是严格单调的或弱单调的,但在没有上下文或明确声明的情况下并不清楚。在浮点运算的上下文中,通常意味着弱单调性,因为浮点舍入通常会破坏强单调性。

\n

在这种用法中,维基百科的意思是,当被视为 的函数时tv0 + t * (v1 - v0)是单调的,但(1 - t) * v0 + t * v1事实并非如此。

\n
\n

为什么一个实现是单调的,而另一个实现却不是?

\n
\n

要了解为什么v0 + t * (v1-v0)是单调的,请考虑将其v1-v0固定为t变化。然后t * (v1-v0)t * c一些常数ct由于浮点舍入的本质,这是单调的:如果t增加,则增加的实数算术结果t * c(对于 正c;对于 负 ,有一个对称参数c),并且它必须舍入的数字保持不变或增加。例如,如果我们四舍五入到整数并考虑 3、3.1、3.2、3.3、3.4 等,那么这些都将四舍五入到 3。然后 3.5 舍入到 4(使用舍入到最近的、连到偶数的方法) ),3.6 轮到 4,依此类推。舍入的结果总是随着参数的增加而增加;它是单调的。所以浮点乘法是单调的。

\n

类似地,浮点加法是单调的;v0 + d总是随着增加而d增加。所以v0 + t * (v1-v0)是单调的。

\n

在 中(1-t) * v0 + t * v11-t是单调的,但它是下降的。所以现在我们将一个降序函数 ,(1-t) * v0添加到一个升序函数 ,t * v1。这为下降函数跳转到新值但上升函数不会跳转到新值或跳转幅度较小的机会打开了大门。对于我们的三位数格式,示例为v0= 123、v1= 223 和t= .126 或 .127:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n
t= .126t= .127
1-t.874.873
(1-t) * v0107.502\xe2\x86\x92 108107.379 \xe2\x86\x92 107
t * v128.098\xe2\x86\x92 28.128.321\xe2\x86\x92 28.3
(1-t) * v0 + t * v1136.1 \xe2\x86\x92 136135.3 \xe2\x86\x92 135
\n
\n
\n
    \n
  1. 还有其他常见的实现吗lerp
  2. \n
\n
\n

正如njuffa在评论中指出的,某些实现可能会使用融合乘法加法,它计算\ xe2 \x80\xa2 b + c时只有一个舍入误差,与乘法的舍入误差和加法的另一个舍入误差形成鲜明对比。此类操作在标准 C 库例程中定义fma,尽管在没有硬件支持的平台上可能会很慢。因此线性插值可以计算为fma(t, v1, fma(-t, v0, v0)),名义上计算t*v1 + (-t*v0 + v0)

\n

从代数上来说,这相当于t*v1 + (1-t)*v0,但我手头没有关于此fma计算得出的数学属性的任何评论。

\n
\n

如果后一种实现存在浮点不精确问题,但速度并不快,为什么它还要存在呢?

\n
\n

这两种方法都会遇到浮点舍入问题。存在当为 1 时v0 + t * (v1 - v0)可能不等于的问题,因为 中可能会出现舍入错误,因此无法恢复。另一个存在的问题是它可能不单调,如上所示。v1tv1 - v0v0 + (v1 - v0)v1

\n