我想计算三维和四维向量的范数(长度)。我使用双精度浮点数,并且要小心避免不必要的上溢或下溢。
C数学库提供hypot(x,y)
来计算二维向量的范数,小心避免中间计算中的下溢/溢出。
我的问题:使用hypot(x, hypot(y, z))
和hypot(hypot(w, x), hypot(y, z))
分别计算三维向量和四维向量的长度是否安全?
答案 0 :(得分:3)
这很安全,但却很浪费:您只需要计算sqrt()
一次,但是当您级联hypot()
时,您会为每个人sqrt()
致电致电hypot()
。通常我可能不关心性能,但这可能也降低了结果的精度。你可以写自己的:
double hypot3(double x, double y, double z) {
return sqrt(x*x + y*y + z*z);
}
等。这将更快,更准确。我不认为当他们在您的代码中看到hypot3()
时会有人感到困惑。
标准库hypot()
可能有一些技巧可以避免溢出,但您可能不会担心它。通常,hypot()
比sqrt(x*x + y*y)
更准确。请参阅GLibC源代码中的e_hypot.c。
答案 1 :(得分:2)
安全(几乎)使用hypot(x, hypot(y, z))
和hypot(hypot(w, x), hypot(y, z))
来计算三维和四维向量的长度。
C没有强烈指明hypot()
必须适用于double x, y
答案有限的double
。它有weasel words“没有过度溢出或下溢”。
然而,鉴于hypot(x, y)
有效,合理的hypot()
实施将根据需要执行hypot(hypot(w, x), hypot(y, z))
。当使用4-D与2-D时,二进制指数范围仅有1个增量(在低端)/减量(在高端)。
关于速度,精度和范围,代码配置文件可以替代sqrtl((long double) w*w + (long double) x*x + (long double) y*y + (long double) z*z)
,但似乎只需要选择编码目标。
答案 2 :(得分:1)
我已经做过一些这方面的实验。特别地,我查看了一个简单的实现,一个使用hypots的实现和BLAS函数DNRM2的(参考版本的C转换)。
我发现关于上溢和下溢,BLAS和hypot实现是相同的(在我的测试中)并且远远优于普通实现。关于时间,对于高(数百)尺寸的载体,BLAS比平原慢约6倍,而hypot比BLAS慢3倍。对于较小的尺寸,时间差异略小。
答案 3 :(得分:1)
如果代码无法使用hypot()
或更宽的精度类型,则慢速方法会使用frexp()
检查指数并缩放argumnets @greggo。
#include <math.h>
double nibot_norm(double w, double x, double y, double z) {
// Sort the values by some means
if (fabs(x) < fabs(w)) return nibot_norm(x, w, y, z);
if (fabs(y) < fabs(x)) return nibot_norm(w, y, x, z);
if (fabs(z) < fabs(y)) return nibot_norm(w, x, z, y);
if (z == 0.0) return 0.0; // all zero case
// Scale z to exponent half-way 1.0 to MAX_DOUBLE/4
// and w,x,y the same amount
int maxi;
frexp(DBL_MAX, &maxi);
int zi;
frexp(z, &zi);
int pow2scale = (maxi / 2 - 2) - zi;
// NO precision loss expected so far.
// except w,x,y may become 0.0 if _far_ less than z
w = ldexp(w, pow2scale);
x = ldexp(x, pow2scale);
y = ldexp(y, pow2scale);
z = ldexp(z, pow2scale);
// All finite values in range of squaring except for values
// greatly insignificant to z (e.g. |z| > |x|*1e300)
double norm = sqrt(((w * w + x * x) + y * y) + z * z);
// Restore scale
return ldexp(norm, -pow2scale);
}
测试代码
#include <float.h>
#include <stdio.h>
#ifndef DBL_TRUE_MIN
#define DBL_TRUE_MIN DBL_MIN*DBL_EPSILON
#endif
void nibot_norm_test(double w, double x, double y, double z, double expect) {
static int dig = DBL_DECIMAL_DIG - 1;
printf(" w:%.*e x:%.*e y:%.*e z:%.*e\n", dig, w, dig, x, dig, y, dig, z);
double norm = nibot_norm(w, x, y, z);
printf("expect:%.*e\n", dig, expect);
printf("actual:%.*e\n", dig, norm);
if (expect != norm) puts("Different");
}
int main(void) {
nibot_norm_test(0, 0, 0, 0, 0);
nibot_norm_test(10 / 7., 4 / 7., 2 / 7., 1 / 7., 11 / 7.);
nibot_norm_test(DBL_MAX, 0, 0, 0, DBL_MAX);
nibot_norm_test(DBL_MAX / 2, DBL_MAX / 2, DBL_MAX / 2, DBL_MAX / 2, DBL_MAX);
nibot_norm_test(DBL_TRUE_MIN, 0, 0, 0, DBL_TRUE_MIN);
nibot_norm_test(DBL_TRUE_MIN, DBL_TRUE_MIN, DBL_TRUE_MIN,
DBL_TRUE_MIN, DBL_TRUE_MIN * 2);
return 0;
}
结果
w:0.00000000000000000e+00 x:0.00000000000000000e+00 y:0.00000000000000000e+00 z:0.00000000000000000e+00
expect:0.00000000000000000e+00
actual:0.00000000000000000e+00
w:1.42857142857142860e+00 x:5.71428571428571397e-01 y:2.85714285714285698e-01 z:1.42857142857142849e-01
expect:1.57142857142857140e+00
actual:1.57142857142857140e+00
w:1.79769313486231571e+308 x:0.00000000000000000e+00 y:0.00000000000000000e+00 z:0.00000000000000000e+00
expect:1.79769313486231571e+308
actual:1.79769313486231571e+308
w:8.98846567431157854e+307 x:8.98846567431157854e+307 y:8.98846567431157854e+307 z:8.98846567431157854e+307
expect:1.79769313486231571e+308
actual:1.79769313486231571e+308
w:4.94065645841246544e-324 x:0.00000000000000000e+00 y:0.00000000000000000e+00 z:0.00000000000000000e+00
expect:4.94065645841246544e-324
actual:4.94065645841246544e-324
w:4.94065645841246544e-324 x:4.94065645841246544e-324 y:4.94065645841246544e-324 z:4.94065645841246544e-324
expect:9.88131291682493088e-324
actual:9.88131291682493088e-324