在执行点积之前标准化向量?
Normalize Vectors Before Performing Dot Product?
我有一个经典的练习,要求我检查一个角色是否被从背后攻击(背刺或射弹)。我知道我可以为此使用点积,我只是想知道我是否必须先对向量进行归一化或者这无关紧要。
我的推理是如果矢量坐标是巨大的数字,它是否会导致我的点积溢出或下溢。
float dotProd(Vector3& P, Vector3& Q)
{
return P.x * Q.x + P.y * Q.y + P.z * Q.z;
}
bool sneakAttack(Vector3& proj, Vector3& player)
{
if(dotProd(proj, player) > 0)
return true;
else
return false;
}
你是对的,原则上,一些坐标可能太大以至于点积计算溢出。
然而,有限 32 位浮点数的可表示范围是巨大的,最大大约为 3 × 10^38(Wikipedia). For instance supposing your vectors are in units of centimeters, that's enough to compute dot products of vectors stretching from Pluto to the Sun (distance of about 6 × 10^14 cm) and still not get close to overflowing. By the "You aren't gonna need it" (YAGNI) 原则,不值得编写例程来处理更大的输入,除非明确知道某些输入实际上具有星际大小。此外,归一化会增加运行时成本,所以我认为除非实际必要,否则最好不要这样做。
一般来说,几乎所有对浮点数或整数进行算术运算的软件在极端情况下都可能发生溢出。如果必须围绕每个操作考虑溢出错误处理,那将是一个沉重的负担。相反,软件的开发通常考虑将输入保持在某个合理范围内,而不正式证明支持的范围是什么或处理 out-of-range 输入,除非应用程序需要它。
规范化可能会使您的代码 更多 可能溢出。
假设向量 P
的长度为 A
,Q
的长度为 B
。为了规范化第一个向量,通常的方法是将每个坐标除以 A
,这是通过首先计算 A2 得到的,恰好等于 P·P
。有问题。如果 A >= B
,则 P·P >= |P·Q|
。因此,如果 P·Q
溢出,则 P·P
溢出。在溢出成为您的点积问题之前,它已经成为标准化问题。
还有一个危险需要注意,这可能是发现您需要处理的值范围的另一个原因。考虑
的情况
P.x = 1024
,
P.y = 0.2
,
P.z = 1024
,
Q.x = 1024
,
Q.y = 0.2
,以及
Q.z = -1024
.
在数学上,这种情况下的点积是 P.y * Q.y
。但是(使用 ^
表示求幂)您的点积计算为 (2^20 + 0.04) - 2^20
。 2^20
和 0.04
的数量级差异对于 23 位尾数来说太大了。所以它们的总和计算为 2^20
。减去 2^20
不会留下任何迹象表明 P.y * Q.y
的符号是什么。规范化在这里没有帮助,因为它只会将问题转移到 2 的较低次方。解决这个问题可能会解决溢出威胁 side-effect.
我有一个经典的练习,要求我检查一个角色是否被从背后攻击(背刺或射弹)。我知道我可以为此使用点积,我只是想知道我是否必须先对向量进行归一化或者这无关紧要。
我的推理是如果矢量坐标是巨大的数字,它是否会导致我的点积溢出或下溢。
float dotProd(Vector3& P, Vector3& Q)
{
return P.x * Q.x + P.y * Q.y + P.z * Q.z;
}
bool sneakAttack(Vector3& proj, Vector3& player)
{
if(dotProd(proj, player) > 0)
return true;
else
return false;
}
你是对的,原则上,一些坐标可能太大以至于点积计算溢出。
然而,有限 32 位浮点数的可表示范围是巨大的,最大大约为 3 × 10^38(Wikipedia). For instance supposing your vectors are in units of centimeters, that's enough to compute dot products of vectors stretching from Pluto to the Sun (distance of about 6 × 10^14 cm) and still not get close to overflowing. By the "You aren't gonna need it" (YAGNI) 原则,不值得编写例程来处理更大的输入,除非明确知道某些输入实际上具有星际大小。此外,归一化会增加运行时成本,所以我认为除非实际必要,否则最好不要这样做。
一般来说,几乎所有对浮点数或整数进行算术运算的软件在极端情况下都可能发生溢出。如果必须围绕每个操作考虑溢出错误处理,那将是一个沉重的负担。相反,软件的开发通常考虑将输入保持在某个合理范围内,而不正式证明支持的范围是什么或处理 out-of-range 输入,除非应用程序需要它。
规范化可能会使您的代码 更多 可能溢出。
假设向量 P
的长度为 A
,Q
的长度为 B
。为了规范化第一个向量,通常的方法是将每个坐标除以 A
,这是通过首先计算 A2 得到的,恰好等于 P·P
。有问题。如果 A >= B
,则 P·P >= |P·Q|
。因此,如果 P·Q
溢出,则 P·P
溢出。在溢出成为您的点积问题之前,它已经成为标准化问题。
还有一个危险需要注意,这可能是发现您需要处理的值范围的另一个原因。考虑
的情况P.x = 1024
,P.y = 0.2
,P.z = 1024
,
Q.x = 1024
,Q.y = 0.2
,以及Q.z = -1024
.
在数学上,这种情况下的点积是 P.y * Q.y
。但是(使用 ^
表示求幂)您的点积计算为 (2^20 + 0.04) - 2^20
。 2^20
和 0.04
的数量级差异对于 23 位尾数来说太大了。所以它们的总和计算为 2^20
。减去 2^20
不会留下任何迹象表明 P.y * Q.y
的符号是什么。规范化在这里没有帮助,因为它只会将问题转移到 2 的较低次方。解决这个问题可能会解决溢出威胁 side-effect.