用矢量计算 3 点之间的角度,需要更高的精度 (java)
Calculate angle between 3 points with vector, more precision needed (java)
我需要计算3个点之间的角度。我已经使用向量完成了这项工作,看起来它在工作,但有时我会得到 NaN 结果。为了计算角度,我使用了 arcos(dot(v1,v2)/(length(v1)*length(v2))) 公式。这是代码:
private static double angleBetween(Point previous, Point center, Point next) {
Vector2D vCenter = new Vector2D(center.x, center.y );
Vector2D vPrev = new Vector2D(previous.x, previous.y );
Vector2D vNext = new Vector2D(next.x, next.y );
Vector2D a = vCenter.minus(vPrev);
Vector2D b = vCenter.minus(vNext);
double dot = Vector2D.dot(a, b);
double la = a.length();
double lb = b.length();
double l = la*lb;
double acos = Math.acos(dot/l);
double deg = Math.toDegrees(acos);
return deg;
}
Vector2D class:
public double length() {
return Math.sqrt(x * x + y * y);
};
public static double dot(Vector2D v1, Vector2D v2) {
return v1.x * v2.x + v1.y * v2.y;
};
public Vector2D minus(Vector2D v) {
return new Vector2D(x - v.x, y - v.y);
};
调试程序我发现了为什么会发生这种情况。例如:
center = (127,356)
previous = (117,358)
next = (137,354)
//calculating the vectors
a = (-10,2) //center - prev
b = (10,-2) //center - next
dot = -10*10 + 2*-2 = 104
la = sqrt(-10*-10 + 2*2) = sqrt(104) = see attachment
lb = sqrt(10*10 + -2*-2) = sqrt(104) = see attachment
l = la * lb = see attachment
acos = NaN
因为 dot/l>1
因为 sqrt()
没有给我准确的值所以我失去了精度因此 la*lb
不是 104.
据我所知,double 是 java 中最精确的数字类型。我该如何解决这个问题?
PS这可能看起来是一种非常罕见的情况,但我遇到了很多,所以我不能忽视它。
共线向量之间的夹角为 180 度。使用 arccos
效果不佳,因为 arccos
使用毕达哥拉斯定理推导角度,而毕达哥拉斯定理仅适用于三角形。两个共线向量不构成三角形。
最简单的答案是检查 Nan
和特殊情况。
double deg = Math.toDegrees(Double.isNaN(acos) ? Math.PI : acos);
解决此问题的最佳方法是使用适当的数据类型,例如 java.math.BigDecimal
,并使用类型 java.math.MathContext
的实例为您的计算定义精度。例如:
double l = la*lb;
BigDecimal lWrapper = new BigDecimal(l, new MathContext(5));
l = lWrapper.doubleValue();
还有另一种方法可以解决这个问题。使用以下公式:
angle = atan(length(crossProduct(a, b)) / dotProduct(a, b)) // Because the domain of definition of the tan function is R
公式推导:
cos(angle) = dotProduct(a, b) / (length(a) * length(b)) and
sin(angle) = length(crossProduct(a, b)) / (length(a) * length(b))
一个有
tan(angle) = sin(angle) / cos(angle)
所以
tan(angle) = length(crossProduct(a, b)) / (length(a) * length(b)) / dotProduct(a, b) / (length(a) * length(b))
tan(angle) = length(crossProduct(a, b)) / dotProduct(a, b)
应用tan的反转函数:
angle = atan(length(crossProduct(a, b)) / dotProduct(a, b))
A、B的叉积∈ℜ2:
|| A x B || = det(A,B) = ((A.x * B.y) - (A.y * B.x))
备注:
- ||x||是向量的长度 x ⇔ length(a)
- ∀ A, B ∈ ℜ2: ||一个 x 乙 ||等于 A, B
的行列式
- 您可以使用符号(|| A x B ||) 找出方向
我需要计算3个点之间的角度。我已经使用向量完成了这项工作,看起来它在工作,但有时我会得到 NaN 结果。为了计算角度,我使用了 arcos(dot(v1,v2)/(length(v1)*length(v2))) 公式。这是代码:
private static double angleBetween(Point previous, Point center, Point next) {
Vector2D vCenter = new Vector2D(center.x, center.y );
Vector2D vPrev = new Vector2D(previous.x, previous.y );
Vector2D vNext = new Vector2D(next.x, next.y );
Vector2D a = vCenter.minus(vPrev);
Vector2D b = vCenter.minus(vNext);
double dot = Vector2D.dot(a, b);
double la = a.length();
double lb = b.length();
double l = la*lb;
double acos = Math.acos(dot/l);
double deg = Math.toDegrees(acos);
return deg;
}
Vector2D class:
public double length() {
return Math.sqrt(x * x + y * y);
};
public static double dot(Vector2D v1, Vector2D v2) {
return v1.x * v2.x + v1.y * v2.y;
};
public Vector2D minus(Vector2D v) {
return new Vector2D(x - v.x, y - v.y);
};
调试程序我发现了为什么会发生这种情况。例如:
center = (127,356)
previous = (117,358)
next = (137,354)
//calculating the vectors
a = (-10,2) //center - prev
b = (10,-2) //center - next
dot = -10*10 + 2*-2 = 104
la = sqrt(-10*-10 + 2*2) = sqrt(104) = see attachment
lb = sqrt(10*10 + -2*-2) = sqrt(104) = see attachment
l = la * lb = see attachment
acos = NaN
因为 dot/l>1
因为 sqrt()
没有给我准确的值所以我失去了精度因此 la*lb
不是 104.
据我所知,double 是 java 中最精确的数字类型。我该如何解决这个问题?
PS这可能看起来是一种非常罕见的情况,但我遇到了很多,所以我不能忽视它。
共线向量之间的夹角为 180 度。使用 arccos
效果不佳,因为 arccos
使用毕达哥拉斯定理推导角度,而毕达哥拉斯定理仅适用于三角形。两个共线向量不构成三角形。
最简单的答案是检查 Nan
和特殊情况。
double deg = Math.toDegrees(Double.isNaN(acos) ? Math.PI : acos);
解决此问题的最佳方法是使用适当的数据类型,例如 java.math.BigDecimal
,并使用类型 java.math.MathContext
的实例为您的计算定义精度。例如:
double l = la*lb;
BigDecimal lWrapper = new BigDecimal(l, new MathContext(5));
l = lWrapper.doubleValue();
还有另一种方法可以解决这个问题。使用以下公式:
angle = atan(length(crossProduct(a, b)) / dotProduct(a, b)) // Because the domain of definition of the tan function is R
公式推导:
cos(angle) = dotProduct(a, b) / (length(a) * length(b)) and
sin(angle) = length(crossProduct(a, b)) / (length(a) * length(b))
一个有
tan(angle) = sin(angle) / cos(angle)
所以
tan(angle) = length(crossProduct(a, b)) / (length(a) * length(b)) / dotProduct(a, b) / (length(a) * length(b))
tan(angle) = length(crossProduct(a, b)) / dotProduct(a, b)
应用tan的反转函数:
angle = atan(length(crossProduct(a, b)) / dotProduct(a, b))
A、B的叉积∈ℜ2:
|| A x B || = det(A,B) = ((A.x * B.y) - (A.y * B.x))
备注:
- ||x||是向量的长度 x ⇔ length(a)
- ∀ A, B ∈ ℜ2: ||一个 x 乙 ||等于 A, B 的行列式
- 您可以使用符号(|| A x B ||) 找出方向