我想出了一个算法,它不仅取平方根,而且取每个 BigDecimal 的整数以下的每个根。
它的一大优势是它不执行搜索算法,因此运行时间为 0.1 毫秒 - 1 毫秒,速度非常快。
但是你得到的速度和多功能性,它缺乏准确性,它平均 5 个正确的数字,第五个数字的偏差为 3。 (用一百万个随机数和根进行测试),虽然测试运行的根非常高,所以如果您将根保持在 10 以下,您可以期待更高的准确度。
结果只有 64 位精度,其余数字为零,因此如果您需要非常高的精度,请不要使用此函数。
它是用来处理非常大的数字和非常大的根,而不是非常小的数字。
public static BigDecimal nrt(BigDecimal bd,int root) {
//if number is smaller then double_max_value it's faster to use the usual math
//library
if(bd.compareTo(BigDecimal.valueOf(Double.MAX_VALUE)) < 0)
return new BigDecimal( Math.pow(bd.doubleValue(), 1D / (double)root ));
BigDecimal in = bd;
int digits = bd.precision() - bd.scale() -1; //take digits to get the numbers power of ten
in = in.scaleByPowerOfTen (- (digits - digits%root) ); //scale down to the lowest number with it's power of ten mod root is the same as initial number
if(in.compareTo(BigDecimal.valueOf( Double.MAX_VALUE) ) > 0) { //if down scaled value is bigger then double_max_value, we find the answer by splitting the roots into factors and calculate them seperately and find the final result by multiplying the subresults
int highestDenominator = highestDenominator(root);
if(highestDenominator != 1) {
return nrt( nrt(bd, root / highestDenominator),highestDenominator); // for example turns 1^(1/25) 1^(1/5)^1(1/5)
}
//hitting this point makes the runtime about 5-10 times higher,
//but the alternative is crashing
else return nrt(bd,root+1) //+1 to make the root even so it can be broken further down into factors
.add(nrt(bd,root-1),MathContext.DECIMAL128) //add the -1 root and take the average to deal with the inaccuracy created by this
.divide(BigDecimal.valueOf(2),MathContext.DECIMAL128);
}
double downScaledResult = Math.pow(in.doubleValue(), 1D /root); //do the calculation on the downscaled value
BigDecimal BDResult =new BigDecimal(downScaledResult) // scale back up by the downscaled value divided by root
.scaleByPowerOfTen( (digits - digits % root) / root );
return BDResult;
}
private static int highestDenominator(int n) {
for(int i = n-1; i>1;i--) {
if(n % i == 0) {
return i;
}
}
return 1;
}
它的工作原理是使用一个数学属性,基本上说,当你做平方根时,你可以将 x^0.5 更改为 (x/100)^0,5 * 10,因此将底数除以 100 取幂并乘以 10 .
一般化后变为 x^(1/n) = (x / 10^n) ^ (1/n) * 10。
因此,对于立方根,您需要将底除以 10^3,对于四根,您需要除以 10^4,依此类推。
该算法使用该函数将输入缩小到数学库可以处理的范围,然后根据输入的缩小程度再次将其重新放大。
它还处理了一些输入无法足够缩小的边缘情况,正是这些边缘情况增加了很多准确性问题。