ProGuard可能导致错误的计算

我遇到了一个非常奇怪的错误。 下面的一小段代码使用一个相当简单的math。

protected double C_n_k(int n, int k) { if(k<0 || k>n) return 0; double s=1; for(int i=1;i<=k;i++) s=s*(n+1-i)/i; return s; } 

编辑使用ProGuard可能会使某些设备出现问题。 我已经在HTC One S Android 4.1.1 build 3.16.401.8上得到确认,但通过电子邮件来看,很多Android 4+手机都受到了影响。 对于其中的一些(Galaxy S3),美国运营商品牌手机受到影响,而国际版本则不受影响。 许多电话不受影响。

下面是计算C(n,k)1 <= n <25和0 <= k <= n的活动代码。 在上面提到的设备上,第一次会话给出了正确的结果,但随后的发射显示不正确的结果,每次都在不同的位置。

我有3个问题:

  1. 怎么会这样? 即使ProGuard出了问题,设备和会话之间的计算也应该是一致的。

  2. 我们怎样才能避免呢? 我知道在这种情况下用long来代替double是可以的,但这不是一个通用的方法。 使用double或释放未混淆的版本丢弃是没有问题的。

  3. 哪些Android版本受到影响? 我在游戏中进行修复的速度很快,所以我只知道很多玩家已经看到了它,而且至less最有Android 4.0

溢出是没有问题的,因为有时我在计算C(3,3)=3/1*2/2*1/3看到错误。 通常不正确的数字从C(10,…)开始,看起来就像一个电话已经“忘记”做一些分裂。

我的SDK工具是22.3(最新的),我已经在Eclipse和IntelliJ IDEA创build的构build中看到过它。

活动代码:

 package com.karmangames.mathtest; import android.app.Activity; import android.os.Bundle; import android.text.method.ScrollingMovementMethod; import android.widget.TextView; public class MathTestActivity extends Activity { /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String s=""; for(int n=0;n<=25;n++) for(int k=0;k<=n;k++) { double v=C_n_k_double(n,k); s+="C("+n+","+k+")="+v+(v==C_n_k_long(n,k) ? "" : " Correct is "+C_n_k_long(n,k))+"\n"; if(k==n) s+="\n"; } System.out.println(s); ((TextView)findViewById(R.id.text)).setText(s); ((TextView)findViewById(R.id.text)).setMovementMethod(new ScrollingMovementMethod()); } protected double C_n_k_double(int n, int k) { if(k<0 || k>n) return 0; //C_n^k double s=1; for(int i=1;i<=k;i++) s=s*(n+1-i)/i; return s; } protected double C_n_k_long(int n, int k) { if(k<0 || k>n) return 0; //C_n^k long s=1; for(int i=1;i<=k;i++) s=s*(n+1-i)/i; return (double)s; } } 

main.xml中:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/text" android:text="Hello World!" /> </LinearLayout> 

错误的计算结果的例子(记住,每次我尝试它都是不同的)

 C(0,0)=1.0 C(1,0)=1.0 C(1,1)=1.0 C(2,0)=1.0 C(2,1)=2.0 C(2,2)=1.0 C(3,0)=1.0 C(3,1)=3.0 C(3,2)=3.0 C(3,3)=1.0 C(4,0)=1.0 C(4,1)=4.0 C(4,2)=6.0 C(4,3)=4.0 C(4,4)=1.0 C(5,0)=1.0 C(5,1)=5.0 C(5,2)=10.0 C(5,3)=10.0 C(5,4)=30.0 Correct is 5.0 C(5,5)=1.0 C(6,0)=1.0 C(6,1)=6.0 C(6,2)=15.0 C(6,3)=40.0 Correct is 20.0 C(6,4)=90.0 Correct is 15.0 C(6,5)=144.0 Correct is 6.0 C(6,6)=120.0 Correct is 1.0 C(7,0)=1.0 C(7,1)=7.0 C(7,2)=21.0 C(7,3)=35.0 C(7,4)=105.0 Correct is 35.0 C(7,5)=504.0 Correct is 21.0 C(7,6)=840.0 Correct is 7.0 C(7,7)=720.0 Correct is 1.0 C(8,0)=1.0 C(8,1)=8.0 C(8,2)=28.0 C(8,3)=112.0 Correct is 56.0 C(8,4)=70.0 C(8,5)=1344.0 Correct is 56.0 C(8,6)=3360.0 Correct is 28.0 C(8,7)=5760.0 Correct is 8.0 C(8,8)=5040.0 Correct is 1.0 C(9,0)=1.0 C(9,1)=9.0 C(9,2)=36.0 C(9,3)=168.0 Correct is 84.0 C(9,4)=756.0 Correct is 126.0 C(9,5)=3024.0 Correct is 126.0 C(9,6)=10080.0 Correct is 84.0 C(9,7)=25920.0 Correct is 36.0 C(9,8)=45360.0 Correct is 9.0 C(9,9)=40320.0 Correct is 1.0 C(10,0)=1.0 C(10,1)=10.0 C(10,2)=45.0 C(10,3)=120.0 C(10,4)=210.0 C(10,5)=252.0 C(10,6)=25200.0 Correct is 210.0 C(10,7)=120.0 C(10,8)=315.0 Correct is 45.0 C(10,9)=16800.0 Correct is 10.0 C(10,10)=1.0 

Solutions Collecting From Web of "ProGuard可能导致错误的计算"

Android团队成员发布了一个可能的解决scheme,对我的问题发表评论。 如果我将android:vmSafeMode="true"到清单文件的application元素,则所有计算都将正确执行。 这个选项没有很好的logging,说实话,我不知道它会影响速度多less,但至lessmath是正确的。 我会将其标记为正确答案,直到find更好的答案。

原始代码和处理后的代码在Java虚拟机和大多数Dalvik虚拟机上工作良好,因此它必须是有效的。 如果处理的代码在一些Dalvik虚拟机上产生虚假的结果,那么这个问题可能是由这些虚拟机中的JIT编译器引起的。 Google的Android团队应该仔细研究一下。

ProGuard在这里应用的最明显的优化是内联方法。 几个分支指令和局部variables在最后的字节码中被重新sorting,但是这个小块代码的执行stream程基本上是一样的。 很难确定ProGuard如何避免这个问题。 您可以完全禁用优化步骤。

您可以检查是否内嵌代码手动导致相同的问题,没有ProGuard(问题似乎没有发生在我的设备上)。

(我是ProGuard的开发者)

这一切都certificate是ProGuard优化刚刚触发的JIT编译器错误。

正如AOSP的问题所解释的那样:

Jelly Bean释放时间中有一个窗口,JIT会错误地优化浮点双常量的使用,这些常量在低32位中是相同的(并且还有一些其他条件满足)。 2012年11月下旬在内部Google树中引入了缺陷,2013年2月在aosp。 在2013年4月修复。

在这个其他的AOSP问题上更详细的解释。