关于Math.atan2的保证

时间:2016-05-20 20:39:52

标签: java math

Math.atan2的文档说

  

计算结果必须在精确结果的2 ulps范围内。

事实上它表示2 ulps可能意味着有些情况下返回值与真实结果不是最接近的double。有谁知道是否保证为等效的int参数对返回相同的值?换句话说,如果abk为正值inta * kb * k都没有溢出,则保证< / p>

Math.atan2(a, b) == Math.atan2(a * k, b * k) 

修改

请注意,对于非溢出long乘法,绝对不是这种情况。例如

long a = 959786689;
long b = 363236985;
long k = 9675271;
System.out.println(Math.atan2(a, b));
System.out.println(Math.atan2(a * k, b * k));

打印

1.2089992287797169
1.208999228779717

但我在int值中找不到示例。

2 个答案:

答案 0 :(得分:6)

编辑:起初我认为可以使用javadoc中的“结果必须是半单调”的要求来回答,但它实际上无法应用,所以我重写了答案

dimo414的answer几乎涵盖了我能说的一切。我只想补充一点:在同一平台上使用StrictMath.atan2时,甚至在使用atan2(y, x) == atan2(y * k, x * k)时,StrictMath都没有正式保证(来自文档)。当然,y / x的实现实际上使用y / x,所以当double恰好是int值时,结果将是相等的(这里我合理地暗示函数是确定性的),但记住它。

回答有关int参数的部分:double保存32位(实际上,更像是31位加一位符号)并且可以用long类型表示而不会丢失精确,所以没有新问题。

您在问题中描述的差异(对于非溢出的long值)是由于将double值转换为Math.atan2时精度下降造成的,它无关使用double本身,它甚至在函数被调用之前发生。 a * k类型can hold只有53位尾数,但在您的情况下double需要54位,因此它会四舍五入到b * k可以表示的最接近的数字({{1}但是没关系,它只需要52位):

long a = 959786689;
long b = 363236985;
long k = 9675271;

System.out.println(a * k);
System.out.println((double) (a * k));
System.out.println((long) (double) (a * k));
System.out.println((long) (double) (a * k) == a * k);

System.out.println(b * k);
System.out.println((double) (b * k));
System.out.println((long) (double) (b * k));
System.out.println((long) (double) (b * k) == b * k);

输出:

9286196318267719
9.28619631826772E15
9286196318267720
false
3514416267097935
3.514416267097935E15
3514416267097935
true

要解决comment

中的示例

我们有double a = 1.02551177480084, b = 1.12312341356234, k = 5;。在这种情况下,aba * kb * k都不能表示为double而不会损失精度。我将使用BigDecimal来演示它,因为它可以显示double的真实(非舍入)值:

double a = 1.02551177480084;
System.out.println("a is            " + new BigDecimal(a));
System.out.println("a * 5 is        " + new BigDecimal(a * 5));
System.out.println("a * 5 should be " + new BigDecimal(a).multiply(new BigDecimal("5")));

输出

a is            1.0255117748008399924941613789997063577175140380859375
a * 5 is        5.12755887400420018451541182002983987331390380859375   // precision loss here
a * 5 should be 5.1275588740041999624708068949985317885875701904296875

可以清楚地看到差异(使用b代替a也可以这样做。)

有一个更简单的测试(因为atan2()基本上使用a/b):

double a = 1.02551177480084, b = 1.12312341356234, k = 5;
System.out.println(a / b == (a * k) / (b * k));

输出

false

答案 1 :(得分:6)

  

是否有人知道是否保证为等效的int参数对返回相同的值?

简单地说,不。 Math文档 是的真实来源,除了您引用的2 ulp限制之外,它不提供任何保证。这是设计的(正如我们将在下面看到的),因此任何其他来源要么暴露实现细节,要么只是错误。

试图以启发式方式找到下限是不切实际的,因为Math的行为被记录为特定于平台:

  

与类StrictMath的某些数值方法不同,类Math的等效函数的所有实现都未定义为返回逐位相同的结果。这种放松允许在不需要严格再现性的情况下实现更好的实施。

因此,即使您在测试中看到更严格的界限,也没有理由相信这些边界可以跨平台,处理器或Java版本移植。

但是,正如Math的文档说明,StrictMath有更明确的行为。记录StrictMath以跨平台执行一致,并且预期与参考实现fdlibm具有相同的行为。那个项目的readme注释:

  

FDLIBM旨在提供一个合理的便携式...参考质量(对于sin,cos,exp,log等主要函数低于一个ulp)数学库。

您可以引用source code for atan2并通过检查其实现来确定精确边界;需要StrictMath.atan2()的任何其他实现来提供与参考实现相同的结果。

值得注意的是,StrictMath.atan2()并未包含与Math.atan2()相同的2 ulp评论。如果重复fdlibm&#34;低于一个ulp&#34;那将会很好。明确地说,我认为这个评论的缺席意味着StrictMath的实现不需要包括那个警告 - 它总是低于一个ulp。

如果您需要精确的结果或跨平台使用StrictMath的稳定结果,请

tl; dr Math以精确度换取速度。