我写了一个程序来计时某些平方根算法
import java.math.BigDecimal;
public class ScienceFairTwo {
public static final BigDecimal TWO = new BigDecimal(2);
public static final BigDecimal SQRT_TWO = new BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727");
public static final BigDecimal TOLERANCE = BigDecimal.ONE.scaleByPowerOfTen(-100);
public static long[] NewtonMethod() {
int iterations = 0; // so far, we haven't done any iterations
BigDecimal a = BigDecimal.ONE; // set a to be one
long start = System.nanoTime(); // start the timer
while(a.subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) { // while our decimal isn't close enough to the square root of two
a = a.add(TWO.divide(a, 100, BigDecimal.ROUND_HALF_UP)).divide(TWO); // set a to (a + 2/a)/2
iterations++; // add one to our iteration counter
}
return new long[] {System.nanoTime() - start, iterations}; // return the time taken and the iterations taken
}
public static long[] BhaskaraBrounckerAlgorithm() {
int iterations = 0; // so far, we haven't done any iterations
BigDecimal a = BigDecimal.ONE; // set a to be one
long start = System.nanoTime(); // start the timer
while(a.subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) { // while our decimal isn't close enough to the square root of two
a = a.add(TWO).divide(a.add(BigDecimal.ONE), 100, BigDecimal.ROUND_HALF_UP); // set a to (a+2)/(a+1)
iterations++; // add one to our iteration counter
}
return new long[] {System.nanoTime() - start, iterations}; // return the time taken and the iterations taken
}
public static long[] MidpointMethod()
{
int iterations = 0; // so far, we haven't done any iterations
BigDecimal a = BigDecimal.ONE; // set a to be one
BigDecimal b = TWO; // set b to be two
long start = System.nanoTime(); // start the timer
while(a.add(b).divide(TWO).subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) { // while our decimals aren't close enough to the square root of two
if(a.multiply(a).subtract(TWO).abs().compareTo(b.multiply(b).subtract(TWO).abs()) == 1) // if a is farther from the square root of two than b
a = a.add(b).divide(TWO); // set a to be the average of a and b
else // if a is closer to the square root of two than b
b = a.add(b).divide(TWO); // set b to be the average of a and b
iterations++; // add one to our iteration counter
}
return new long[] {System.nanoTime() - start, iterations}; // return the time taken and the iterations taken
}
public static long[] SecantMethod()
{
BigDecimal a = BigDecimal.ONE; // set a to be one
BigDecimal b = TWO; // set b to be two
BigDecimal b_old = TWO; // set b_old to be two (this is a transferring variable)
long start = System.nanoTime(); // start the timer
int iterations = 0; // so far, we haven't done any iterations
while(a.add(b).divide(TWO).subtract(SQRT_TWO).abs().compareTo(TOLERANCE) >= 0) { // // while our decimals aren't close enough to the square root of two
b_old = b; // set b_old to be b
b = a.multiply(b).add(TWO).divide(a.add(b), 100, BigDecimal.ROUND_HALF_UP); // set b to be (ab + 2)/(a+b)
a = b_old; // set a to be the previous value of b
iterations++; // add one to our iterations counter
}
return new long[] {System.nanoTime() - start, iterations}; // return the time taken and the iterations taken
}
public static void main(String[] args) {
System.out.printf("Newton Iteration: %f milliseconds (%d iterations taken)\n", NewtonMethod()[0] / 10e6, NewtonMethod()[1]); // print the results
System.out.printf("Midpoint Method: %f milliseconds (%d iterations taken)\n", MidpointMethod()[0] / 10e6, MidpointMethod()[1]); // print the results
System.out.printf("Secant Method: %f milliseconds (%d iterations taken)\n", SecantMethod()[0] / 10e6, SecantMethod()[1]); // print the results
System.out.printf("Bhaskara-Brouncker Algorithm: %f milliseconds (%d iterations taken)\n", BhaskaraBrounckerAlgorithm()[0] / 10e6, BhaskaraBrounckerAlgorithm()[1]); // print the results
}
}
我不想做 100/1000 次试验,因为我需要把这个程序放在科学展览板上,所以我不想通过使用基准测试等使程序复杂化。但是,当我这样做时,结果我变化很大。例如,一旦我得到
牛顿迭代:0.466200 毫秒(进行了 8 次迭代) 中点法:21.090700 毫秒(进行了 330 次迭代) 正割法:0.134500 毫秒(进行了 11 次迭代) Bhaskara-Brouncker 算法:1.315300 毫秒(进行了 132 次迭代)
另一次我得到牛顿迭代:0.550700 毫秒(进行了 8 次迭代)中点方法:23.168400 毫秒(进行了 330 次迭代)正割法:0.130400 毫秒(进行了 11 次迭代) Bhaskara-Brouncker 算法:1.078100 毫秒(进行了 132 次迭代)
现在我得到牛顿迭代:0.469500 毫秒(进行了 8 次迭代)中点方法:22.437700 毫秒(进行了 330 次迭代)正割法:0.189200 毫秒(进行了 11 次迭代) Bhaskara-Brouncker 算法:1.807600 毫秒(进行了 132 次迭代)
他们只是不是很接近。为什么会这样?我每次都在做同样的事情,而 java 不可能优化,因为我每次运行时只做一次。
那么最好的方法是什么?有几种可能的解决方案:
只做一次试验
- 优点:Java无法优化
- 缺点:不是很一致(如上所示)
做 1000 次试验
- 优点:相当一致
- 缺点:java会优化所以结果不准确
进行 1000 次试验,但每次使用 rand 更改我们近似平方根的值
- 优点:相当准确,无需优化
- 缺点:由于随机性,每次答案都会有所不同
做 969 次试验,但每次都做 2, 3, ..., 1000 的平方根(不包括 1,4,..,961)
- 优点:相当准确,无需优化
- 缺点:
我倾向于最后一个。这是一个好主意吗?