我们使用以下代码计算两条线之间的锐角。
def AcuteAngle2(line1,line2):
''':: line(x1,y1,x2,y2)'''
u = (line1[2]-line1[0], line1[3]-line1[1])
v = (line2[2]-line2[0], line2[3]-line2[1])
return arccos(abs(dot(u,v)/(norm(u)*norm(v))))
它按预期工作。例如:
>>> AcuteAngle2([0,0,1,0],[0,0,0,1])
1.5707963267948966 #in rad = 90 degree
但是我们最近发现它在某些特殊情况下失败了!
>>> AcuteAngle2([0,0,1,0],[0,0,1,0])
0.0
这是正确的,但是:
>>> AcuteAngle2([0,0,1,1],[0,0,1,1])
2.1073424255447017e-08 #failed!
哪个不对!它应该是0.0 任何想法和解决方案?
更新1:
如下所示,在答案中使用Decimal
包可能对某些情况有所帮助。然而,我们的问题仍未得到解决,因为(1)有许多代码需要一定的时间来调整每个部分以使用Decimal
。此外,(2)性能显着下降。此外,它还需要(3)在处理numpy
数组时进行大量更改。因此,它对我们的案例没用。我们正在考虑某种装饰器等,而不会改变事物并保持numpy
性能。顺便说一句,有些人可能会建议使用gmpy等多精度软件包,请注意它们需要在代码中进行大量修改,这对我们的情况没有帮助。
答案 0 :(得分:4)
一种选择是使用decimal模块来提高计算的精确度:
from decimal import Decimal, getcontext
def AcuteAngle2(line1,line2):
''':: line(x1,y1,x2,y2)'''
u = (Decimal(line1[2]-line1[0]), Decimal(line1[3]-line1[1]))
v = (Decimal(line2[2]-line2[0]), Decimal(line2[3]-line2[1]))
return arccos(float(abs(dot(u,v)/(norm(u)*norm(v)))))
看起来默认精度为28个位置,您将获得预期答案:
>>> getcontext().prec
28
>>> AcuteAngle2([0,0,1,1],[0,0,1,1])
0.0
答案 1 :(得分:4)
如果您关心准确性,使用arccos
作为锐角是一个坏主意。问题是,对于接近0的小角度变化,该角度的余弦几乎不会改变。对于arccos
情况是相反的 - 对于余弦角的非常小的变化更多。
在2D和3D中,更好的方法是使用atan2(crossproduct.length,scalarproduct)
在2D中,这变为atan2( dx1*dy2-dx2*dy1 , dx1*dy1+dx2*dy2 )
。请注意,您无需规范化向量,因此有两项改进:
arccos
答案 2 :(得分:2)
在您的示例中,计算dot(u,v)/(norm(u)*norm(v))
时会引入舍入误差。对于您的测试值,计算有效2/(sqrt(2)*sqrt(2))
。 sqrt(2)的计算值四舍五入为略大于无限精度值的值。
>>> math.sqrt(2)
1.4142135623730951
>>> math.sqrt(2)*math.sqrt(2)
2.0000000000000004
>>> 2/(math.sqrt(2)*math.sqrt(2))
0.9999999999999998
>>> math.acos(2/(math.sqrt(2)*math.sqrt(2)))
2.1073424255447017e-08
@ F.J的decimal
模块解决方案将2/(sqrt(2)*sqrt(2))
计算得更高。当此值转换为float(通过arccos)时,它将舍入为1.0。
>>> import decimal
>>> decimal.getcontext().sqrt(2)
Decimal('1.414213562373095048801688724')
>>> decimal.getcontext().sqrt(2)**2
Decimal('1.999999999999999999999999999')
>>> 2/decimal.getcontext().sqrt(2)**2
Decimal('1.000000000000000000000000001')
>>> float(2/decimal.getcontext().sqrt(2)**2)
1.0
使用2/(sqrt(2)*sqrt(2))
计算decimal
,不同的精度突出了另一个问题。
>>> for i in range(10,30):
... decimal.getcontext().prec=i
... print i,2/decimal.getcontext().sqrt(2)**2
...
10 1.000000001
11 0.99999999995
12 1.00000000001
13 1
14 1
15 0.999999999999995
16 1
17 1.0000000000000001
18 1
19 0.9999999999999999995
20 1
21 1
22 0.9999999999999999999995
23 1
24 1
25 0.9999999999999999999999995
26 1.0000000000000000000000001
27 1.00000000000000000000000001
28 1.000000000000000000000000001
29 1
结果可能正好是1,小于1或大于1.如果您可以先取arccos
而不先舍入到浮点数,这可能会造成混淆。对于大于1的值,未定义arccos
,因此结果为nan
。如果看到这种类型的舍入误差在中间值超过1时打破纬度/经度计算。只是增加所有计算的精度,例如从float64到float128,将无法解决问题。它可能会将问题移到另一组值,但仍会出现舍入错误。
更新1
还有其他几种选择。您可以将公式重写为:
def AcuteAngle3(line1,line2):
''':: line(x1,y1,x2,y2)'''
u = (line1[2]-line1[0], line1[3]-line1[1])
v = (line2[2]-line2[0], line2[3]-line2[1])
return arccos(sqrt(abs(dot(u,v)**2/(dot(u,u)*dot(v,v)))))
AcuteAngle3
可以避免您的原始问题,但dot(u,u)*dot(v,v)
可能会舍入到一个数值略小于实际值的值,您可以尝试取值大于的值的arccos 1.(但是对于整个表达式只使用ROUND_UP或ROUND_DOWN将不起作用;我尝试在我的decimal
示例中使用不同的舍入模式,并且仍然存在一些舍入“错误”。)
以下函数检查这些异常事件:
def AcuteAngle4(line1,line2):
''':: line(x1,y1,x2,y2)'''
u = (line1[2]-line1[0], line1[3]-line1[1])
v = (line2[2]-line2[0], line2[3]-line2[1])
temp = sqrt(abs(dot(u,v)**2/(dot(u,u)*dot(v,v))))
if temp > 1:
return 0.0
else:
return arccos(temp)
在计算表达式的每个组成部分时,将中间值计算到更高的精度然后向下舍入,或者选择性地向0或远离0舍入,是其他可能性。