Floating point linear interpolation中,一位用户提出了lerp的这种实现:

float lerp(float a, float b, float f) 
{
    return (a * (1.0 - f)) + (b * f);
}

而另一位用户则建议实施lerp:

float lerp(float a, float b, float f)
{
    return a + f * (b - a);
}

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

然后我查看了Wikipedia,它表示对于前一个实现:

// Precise method, which guarantees v = v1 when t = 1. This method is monotonic only when v0 * v1 < 0.
// Lerping between same values might not produce the same value

对于后者:

// Imprecise method, which does not guarantee v = v1 when t = 1, due to floating-point arithmetic error.
// This method is monotonic. This form may be used when the hardware has a native fused multiply-add instruction.

这给了我几个问题:

  1. Wikipedia声明"相同值之间的Lerping可能不会产生相同的值",我认为除了浮点精度之外,函数是相同的.不是的

    (a * (1.0 - f)) + (b * f)=a + f * (b - a)
    

    a mathematical identity?

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

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

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

Edit:

推荐答案

  1. Wikipedia声明"相同值之间的Lerping可能不会产生相同的值",我认为除了浮点精度之外,函数是相同的.不是的

    (a * (1.0 - f)) + (b * f)=a + f * (b - a)
    

    a mathematical identity?

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

a • (1.0−f) + (bf) = a + f • (ba)是一个数学恒等式,但计算a*(1.0-f) + b*f的结果并不总是等于计算a + f*(b-a)的结果.

例如,考虑一种以十进制为基数、有效位中有三位数字的浮点格式.设a为123,b为223,f为.124

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

另一方面,b-a产生b-a.然后f*100产生12.4.然后a + 12.4将产生135.4,但产生135.

因此左右两侧136和135的计算结果不相等.

  1. 维基百科所说的单调是什么意思?

如果参数越大,结果越大,则函数f(x)严格升序:xx意味着f(x<0)<;f(x).如果x0,则呈弱上升趋势;x表示f(x表示0)≤ f(x).如果x0,则为严格递减或弱递减;x表示f(x0)>;f(x)或f(x)≥ f(分别为x1).如果函数严格升序或严格降序,则它是严格单调的.如果它是弱上升或弱下降的,则它是弱单调的.

当一个函数被称为单调函数时,作者的意思是它是严格单调的还是弱单调的,但在没有上下文或明确陈述的情况下,不清楚是哪一个.在浮点算术的上下文中,通常指弱单调性,因为浮点舍入通常会 destruct 强单调性.

在这种用法中,维基百科意味着,当被视为t的函数时,v0 + t * (v1 - v0)是单调的,(1 - t) * v0 + t * v1不是.

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

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

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

(1-t) * v0 + t * v1中,1-t是单调的,但它是递减的.现在我们在升序函数t * v1的基础上加上一个降序函数(1-t) * v0.这为降序函数跳转到一个新值,但升序函数没有跳转到一个新值,或跳转幅度不够的情况打开了大门.对于我们的三位数格式,例如v0=123、v1=223和t=.126或.127:

t = .126 t = .127
1-t .874 .873
(1-t) * v0 107.502 → 108 107.379 → 107
t * v1 28.098 → 28.1 28.321 → 28.3
(1-t) * v0 + t * v1 136.1 → 136 135.3 → 135
  1. lerp还有其他常见的实现吗?

我无法发表 comments .

如果后一种实现存在浮点不精确的问题,但速度不是很快,那么它为什么会存在呢?

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

C++相关问答推荐

如果实际的syscall是CLONE(),那么为什么strace接受fork()呢?

如何在c++中包装返回空*的函数

将宏值传递给ARM链接器,该链接器将变量放置在特定位置

解决S随机内存分配问题,实现跨进程高效数据共享

有什么方法可以将字符串与我们 Select 的子字符串分开吗?喜欢:SIN(LOG(10))

#定义SSL_CONNECTION_NO_CONST

C I/O:在Windows控制台上处理键盘输入

CS50判断灯泡运动的问题,判断时多出一个灯泡,但不在终端上

使用正则表达式获取字符串中标记的开始和结束

C++中PUTS函数的返回值

Realloc():中止的下一个大小无效(核心转储)

用于计算位数和的递归C函数

C编译和运行

为什么我在我的代码中得到错误和退出代码-1073741819(0xC0000005),但如果我添加了一个不相关的打印语句,它仍然有效?

在Ubuntu上使用库部署C程序的最佳实践

解密Chrome加密密钥

模仿 memmove 的行为

使用共享变量同步多线程 C 中的函数

clion.我无法理解 Clion 中发生的 scanf 错误

读取包含指向列表的指针的段寄存器 (%gs)