Math.atan2的文档说
计算结果必须在精确结果的2 ulps范围内。
事实上它表示2 ulps可能意味着有些情况下返回值与真实结果不是最接近的double
。有谁知道是否保证为等效的int
参数对返回相同的值?换句话说,如果a
,b
和k
为正值int
且a * k
和b * 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
值中找不到示例。
答案 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;
。在这种情况下,a
,b
,a * k
,b * 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
以精确度换取速度。