强大的线性插值

时间:2016-10-01 07:26:30

标签: geometry precision

给定两个段端点A和B(二维),我想基于值t进行线性插值,即:

C = A + t(B-A)

在理想世界中,A,B和C应该是共线的。但是,我们在此处使用有限的浮点运算,因此会出现小的偏差。为了解决其他操作中的数值问题,我正在使用最初由Jonathan Shewchuk创建的强大的自适应例程。特别是,Shewchuk实现了一个方向函数orient2d,它使用自适应精度来精确测试三个点的方向。

这里我的问题:是否有一个已知的程序如何使用浮点数学计算插值,以便它恰好位于A和B之间的线上?在这里,我不太关心插值本身的准确性,更关心最终的共线性。换句话说,只要满足共线性,只要C移动一点就可以了。

1 个答案:

答案 0 :(得分:0)

坏消息

无法满足请求。有AB的值,除了t是浮点数的0和1之外,没有lerp(A, B, t)的值。

一个简单的单精度示例是x1 = 12345678.fx2 = 12345679.f。无论y1y2的值如何,要求的结果都必须在x12345678.f之间有一个12345679.f分量,并且两者之间没有单精度浮点数这两个。

(好消息)好消息

但是,精确的插值可以表示为5个浮点值的总和(在2D情况下为矢量):一个用于公式的结果,一个用于每个操作中的错误[1],另一个用于将错误乘以t。我不确定这对您是否有用。为简单起见,这是单精度算法的一维C版本,使用融合乘法加法来计算乘积误差,

#include <math.h>

float exact_sum(float a, float b, float *err)
{
    float sum = a + b;
    float z = sum - a;
    *err = a - (sum - z) + (b - z);
    return sum;
}

float exact_mul(float a, float b, float *err)
{
    float prod = a * b;
    *err = fmaf(a, b, -prod);
    return prod;
}

float exact_lerp(float A, float B, float t,
                 float *err1, float *err2, float *err3, float *err4)
{
    float diff = exact_sum(B, -A, err1);
    float prod = exact_mul(diff, t, err2);
    *err1 = exact_mul(*err1, t, err4);
    return exact_sum(A, prod, err3);
}

为了使该算法起作用,操作需要在最近舍入模式下符合IEEE-754语义。 C标准并不能保证这一点,但是至少在支持SSE2 [2] [3]的处理器中,可以指示GNU gcc 编译器这样做。

保证(result + err1 + err2 + err3 + err4)的算术加等于期望的结果;但是,不能保证这些数量的浮点加法将是准确的。

要使用上面的示例,exact_lerp(12345678.f, 12345679.f, 0.300000011920928955078125f, &err1, &err2, &err3, &err4)返回结果12345678.f,而err1err2err3err4是{{ 1}},0.0f0.0f0.300000011920928955078125f。确实,正确的结果是12345678.300000011920928955078125,它不能表示为单精度浮点数。

一个更复杂的示例:0.0f返回exact_lerp(0.23456789553165435791015625f, 7.345678806304931640625f, 0.300000011920928955078125f, &err1, &err2, &err3, &err4),错误为2.3679010868072509765625f6.7055225372314453125e-08f8.4771045294473879039287567138671875e-08f1.490116119384765625e-08f。这些数字加起来就是准确的结果,即2.36790125353468468550173374751466326415538787841796875,不能精确地存储在单精度浮点数中。

以上示例中的所有数字均使用其确切值而不是近似值书写。例如,不能将0.3精确地表示为单精度浮点数;最接近的精确值为0.300000011920928955078125,这是我使用过的精确值。

如果按此顺序计算2.66453525910037569701671600341796875e-15f,则可能会得到一个在用例中被视为共线的近似值。也许值得一试。

参考文献