我们是否可以仅使用Java API而不是定制的100行算法来计算Java中BigDecimal
的平方根?
答案 0 :(得分:31)
我用过这个并且效果很好。 Here's an example of how the algorithm works at a high level.
编辑:我很想知道下面定义的准确程度。这是official source:
中的sqrt(2)(first 200 digits) 1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206057147
这里使用我在下面概述的方法,SQRT_DIG
等于150:
(first 200 digits) 1.41421356237309504880168872420969807856967187537694807317667973799073247846210703885038753432764157273501384623091229702492483605585073721264412149709993583141322266592750559275579995050115278206086685
第一个偏差发生在 195位精度之后。如果您需要如此高的精确度,请自担风险。
将SQRT_DIG
更改为1000会产生 1570位精度。
private static final BigDecimal SQRT_DIG = new BigDecimal(150);
private static final BigDecimal SQRT_PRE = new BigDecimal(10).pow(SQRT_DIG.intValue());
/**
* Private utility method used to compute the square root of a BigDecimal.
*
* @author Luciano Culacciatti
* @url http://www.codeproject.com/Tips/257031/Implementing-SqrtRoot-in-BigDecimal
*/
private static BigDecimal sqrtNewtonRaphson (BigDecimal c, BigDecimal xn, BigDecimal precision){
BigDecimal fx = xn.pow(2).add(c.negate());
BigDecimal fpx = xn.multiply(new BigDecimal(2));
BigDecimal xn1 = fx.divide(fpx,2*SQRT_DIG.intValue(),RoundingMode.HALF_DOWN);
xn1 = xn.add(xn1.negate());
BigDecimal currentSquare = xn1.pow(2);
BigDecimal currentPrecision = currentSquare.subtract(c);
currentPrecision = currentPrecision.abs();
if (currentPrecision.compareTo(precision) <= -1){
return xn1;
}
return sqrtNewtonRaphson(c, xn1, precision);
}
/**
* Uses Newton Raphson to compute the square root of a BigDecimal.
*
* @author Luciano Culacciatti
* @url http://www.codeproject.com/Tips/257031/Implementing-SqrtRoot-in-BigDecimal
*/
public static BigDecimal bigSqrt(BigDecimal c){
return sqrtNewtonRaphson(c,new BigDecimal(1),new BigDecimal(1).divide(SQRT_PRE));
}
一定要查看barwnikk的答案。它更简洁,看似提供更好或更好的精度。
答案 1 :(得分:28)
public static BigDecimal sqrt(BigDecimal A, final int SCALE) {
BigDecimal x0 = new BigDecimal("0");
BigDecimal x1 = new BigDecimal(Math.sqrt(A.doubleValue()));
while (!x0.equals(x1)) {
x0 = x1;
x1 = A.divide(x0, SCALE, ROUND_HALF_UP);
x1 = x1.add(x0);
x1 = x1.divide(TWO, SCALE, ROUND_HALF_UP);
}
return x1;
}
这项工作完美!非常快,超过65536位!
答案 2 :(得分:11)
从Java 9开始,您可以!参见BigDecimal.sqrt()
。
答案 3 :(得分:8)
通过使用Karp的技巧,只需两行就可以实现这一点,提供32位精度:
public static BigDecimal sqrt(BigDecimal value) {
BigDecimal x = new BigDecimal(Math.sqrt(value.doubleValue()));
return x.add(new BigDecimal(value.subtract(x.multiply(x)).doubleValue() / (x.doubleValue() * 2.0)));
}
答案 4 :(得分:5)
如果您只需查找integer square roots - 这两种方法都可以使用。
Newton's method - 即使1000位数字也非常快BigInteger:
public static BigInteger sqrtN(BigInteger in) {
final BigInteger TWO = BigInteger.valueOf(2);
int c;
// Significantly speed-up algorithm by proper select of initial approximation
// As square root has 2 times less digits as original value
// we can start with 2^(length of N1 / 2)
BigInteger n0 = TWO.pow(in.bitLength() / 2);
// Value of approximate value on previous step
BigInteger np = in;
do {
// next approximation step: n0 = (n0 + in/n0) / 2
n0 = n0.add(in.divide(n0)).divide(TWO);
// compare current approximation with previous step
c = np.compareTo(n0);
// save value as previous approximation
np = n0;
// finish when previous step is equal to current
} while (c != 0);
return n0;
}
Bisection method - 比牛顿慢50倍 - 仅用于教育目的:
public static BigInteger sqrtD(final BigInteger in) {
final BigInteger TWO = BigInteger.valueOf(2);
BigInteger n0, n1, m, m2, l;
int c;
// Init segment
n0 = BigInteger.ZERO;
n1 = in;
do {
// length of segment
l = n1.subtract(n0);
// middle of segment
m = l.divide(TWO).add(n0);
// compare m^2 with in
c = m.pow(2).compareTo(in);
if (c == 0) {
// exact value is found
break;
} else if (c > 0) {
// m^2 is bigger than in - choose left half of segment
n1 = m;
} else {
// m^2 is smaller than in - choose right half of segment
n0 = m;
}
// finish if length of segment is 1, i.e. approximate value is found
} while (l.compareTo(BigInteger.ONE) > 0);
return m;
}
答案 5 :(得分:1)
如果你想计算数字的平方根,而这些数字的位数超过双精度数(大尺度的BigDecimal):
维基百科有一篇关于计算平方根的文章:http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method
这是我的实现:
public static BigDecimal sqrt(BigDecimal in, int scale){
BigDecimal sqrt = new BigDecimal(1);
sqrt.setScale(scale + 3, RoundingMode.FLOOR);
BigDecimal store = new BigDecimal(in.toString());
boolean first = true;
do{
if (!first){
store = new BigDecimal(sqrt.toString());
}
else first = false;
store.setScale(scale + 3, RoundingMode.FLOOR);
sqrt = in.divide(store, scale + 3, RoundingMode.FLOOR).add(store).divide(
BigDecimal.valueOf(2), scale + 3, RoundingMode.FLOOR);
}while (!store.equals(sqrt));
return sqrt.setScale(scale, RoundingMode.FLOOR);
}
setScale(scale + 3, RoundingMode.Floor)
因为过度计算会提高准确性。 RoundingMode.Floor
截断数字,RoundingMode.HALF_UP
执行正常舍入。
答案 6 :(得分:1)
这是非常准确和快速的解决方案,它基于我的BigIntSqRoot solution和下一个观察: A ^ 2B的平方根 - 是A乘以B的根。使用这种方法,我可以很容易地计算出2的平方根的前1000位数。
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457502877599617298355752203375318570113543746034084988471603868999706990048150305440277903164542478230684929369186215805784631115966687130130156185689872372352885092648612494977154218334204285686060146824720771435854874155657069677653720226485447015858801620758474922657226002085584466521458398893944370926591800311388246468157082630100594858704003186480342194897278290641045072636881313739855256117322040245091227700226941127573627280495738108967504018369868368450725799364729060762996941380475654823728997180326802474420629269124859052181004459842150591120249441341728531478105803603371077309182869314710171111683916581726889419758716582152128229518488472
所以这是源代码
public class BigIntSqRoot {
private static final int PRECISION = 10000;
private static BigInteger multiplier = BigInteger.valueOf(10).pow(PRECISION * 2);
private static BigDecimal root = BigDecimal.valueOf(10).pow(PRECISION);
private static BigInteger two = BigInteger.valueOf(2L);
public static BigDecimal bigDecimalSqRootFloor(BigInteger x)
throws IllegalArgumentException {
BigInteger result = bigIntSqRootFloor(x.multiply(multiplier));
//noinspection BigDecimalMethodWithoutRoundingCalled
return new BigDecimal(result).divide(root);
}
public static BigInteger bigIntSqRootFloor(BigInteger x)
throws IllegalArgumentException {
if (checkTrivial(x)) {
return x;
}
if (x.bitLength() < 64) { // Can be cast to long
double sqrt = Math.sqrt(x.longValue());
return BigInteger.valueOf(Math.round(sqrt));
}
// starting with y = x / 2 avoids magnitude issues with x squared
BigInteger y = x.divide(two);
BigInteger value = x.divide(y);
while (y.compareTo(value) > 0) {
y = value.add(y).divide(two);
value = x.divide(y);
}
return y;
}
public static BigInteger bigIntSqRootCeil(BigInteger x)
throws IllegalArgumentException {
BigInteger y = bigIntSqRootFloor(x);
if (x.compareTo(y.multiply(y)) == 0) {
return y;
}
return y.add(BigInteger.ONE);
}
private static boolean checkTrivial(BigInteger x) {
if (x == null) {
throw new NullPointerException("x can't be null");
}
if (x.compareTo(BigInteger.ZERO) < 0) {
throw new IllegalArgumentException("Negative argument.");
}
return x.equals(BigInteger.ZERO) || x.equals(BigInteger.ONE);
}
}
答案 7 :(得分:0)
java api中没有任何内容,所以如果double不够准确(如果没有,为什么要使用BigDecimal?)那么你需要类似下面的代码。)
import java.math.BigDecimal;
public class BigDSqrt {
public static BigDecimal sqrt(BigDecimal n, int s) {
BigDecimal TWO = BigDecimal.valueOf(2);
// Obtain the first approximation
BigDecimal x = n
.divide(BigDecimal.valueOf(3), s, BigDecimal.ROUND_DOWN);
BigDecimal lastX = BigDecimal.valueOf(0);
// Proceed through 50 iterations
for (int i = 0; i < 50; i++) {
x = n.add(x.multiply(x)).divide(x.multiply(TWO), s,
BigDecimal.ROUND_DOWN);
if (x.compareTo(lastX) == 0)
break;
lastX = x;
}
return x;
}
}
答案 8 :(得分:0)
public static BigDecimal sqrt( final BigDecimal value )
{
BigDecimal guess = value.multiply( DECIMAL_HALF );
BigDecimal previousGuess;
do
{
previousGuess = guess;
guess = sqrtGuess( guess, value );
} while ( guess.subtract( previousGuess ).abs().compareTo( EPSILON ) == 1 );
return guess;
}
private static BigDecimal sqrtGuess( final BigDecimal guess,
final BigDecimal value )
{
return guess.subtract( guess.multiply( guess ).subtract( value ).divide( DECIMAL_TWO.multiply( guess ), SCALE, RoundingMode.HALF_UP ) );
}
private static BigDecimal epsilon()
{
final StringBuilder builder = new StringBuilder( "0." );
for ( int i = 0; i < SCALE - 1; ++i )
{
builder.append( "0" );
}
builder.append( "1" );
return new BigDecimal( builder.toString() );
}
private static final int SCALE = 1024;
private static final BigDecimal EPSILON = epsilon();
public static final BigDecimal DECIMAL_HALF = new BigDecimal( "0.5" );
public static final BigDecimal DECIMAL_TWO = new BigDecimal( "2" );
答案 9 :(得分:0)
如前所述:如果你不介意你的答案精确度,但只想在第15个仍然有效之后生成随机数字,那你为什么要使用BigDecimal呢?
以下是应该使用浮点BigDecimals的方法的代码:
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
public BigDecimal bigSqrt(BigDecimal d, MathContext mc) {
// 1. Make sure argument is non-negative and treat Argument 0
int sign = d.signum();
if(sign == -1)
throw new ArithmeticException("Invalid (negative) argument of sqrt: "+d);
else if(sign == 0)
return BigDecimal.ZERO;
// 2. Scaling:
// factorize d = scaledD * scaleFactorD
// = scaledD * (sqrtApproxD * sqrtApproxD)
// such that scalefactorD is easy to take the square root
// you use scale and bitlength for this, and if odd add or subtract a one
BigInteger bigI=d.unscaledValue();
int bigS=d.scale();
int bigL = bigI.bitLength();
BigInteger scaleFactorI;
BigInteger sqrtApproxI;
if ((bigL%2==0)){
scaleFactorI=BigInteger.ONE.shiftLeft(bigL);
sqrtApproxI=BigInteger.ONE.shiftLeft(bigL/2);
}else{
scaleFactorI=BigInteger.ONE.shiftLeft(bigL-1);
sqrtApproxI=BigInteger.ONE.shiftLeft((bigL-1)/2 );
}
BigDecimal scaleFactorD;
BigDecimal sqrtApproxD;
if ((bigS%2==0)){
scaleFactorD=new BigDecimal(scaleFactorI,bigS);
sqrtApproxD=new BigDecimal(sqrtApproxI,bigS/2);
}else{
scaleFactorD=new BigDecimal(scaleFactorI,bigS+1);
sqrtApproxD=new BigDecimal(sqrtApproxI,(bigS+1)/2);
}
BigDecimal scaledD=d.divide(scaleFactorD);
// 3. This is the core algorithm:
// Newton-Ralpson for scaledD : In case of f(x)=sqrt(x),
// Heron's Method or Babylonian Method are other names for the same thing.
// Since this is scaled we can be sure that scaledD.doubleValue() works
// for the start value of the iteration without overflow or underflow
System.out.println("ScaledD="+scaledD);
double dbl = scaledD.doubleValue();
double sqrtDbl = Math.sqrt(dbl);
BigDecimal a = new BigDecimal(sqrtDbl, mc);
BigDecimal HALF=BigDecimal.ONE.divide(BigDecimal.ONE.add(BigDecimal.ONE));
BigDecimal h = new BigDecimal("0", mc);
// when to stop iterating? You start with ~15 digits of precision, and Newton-Ralphson is quadratic
// in approximation speed, so in roundabout doubles the number of valid digits with each step.
// This fmay be safer than testing a BigDecifmal against zero.
int prec = mc.getPrecision();
int start = 15;
do {
h = scaledD.divide(a, mc);
a = a.add(h).multiply(HALF);
start *= 2;
} while (start <= prec);
// 3. Return rescaled answer. sqrt(d)= sqrt(scaledD)*sqrtApproxD :
return (a.multiply(sqrtApproxD));
}
作为测试,尝试重复对数字进行重复几次,而不是重复平方根,看看你离开始的距离有多近。
答案 10 :(得分:0)
我解决这个问题已经有几天了,他想出了一种算法,它不仅可以求平方根,而且还可以计算每个BigDecimal的整数以下的根。
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;
}
请注意,此方法不能再为您提供更多的数字,因此可以将其保留为双精度。 因此,如果您需要高精度,则此方法不适合您。 但是,如果您需要做一个很大的数的根,那么这样做可以相当快并且达到可接受的准确性。 如果根是大质数,则速度特别慢;而对于高度可分的根,速度更快。
之所以起作用是因为n ^(x / a)=(n * 10 ^ a)^(x / a)/ 10 这意味着我们可以取一个很大的数字,然后将其缩小到两倍可以处理的大小,然后根据最初缩小的数量将其放大。
经过一百万次迭代后,我测试了平均误差至1.000384947520725(降低0.03%)。 这并不像我希望的那样好,但是当您使用双精度数并缩小25000位数字时,肯定会有一些误差。我的基准测试给了我这些结果:
10万个5098rt,平均数为3.599916E + 150737 在11786ms
10万个3766rt,平均数为1.606334E + 46239 在4195毫秒内
10万个6849rt,平均数为1.624569E + 291918 在8888毫秒内
100 000 733rt,平均数为1.688336E + 14797 536ms
100 000 841rt,平均数为7.201332E + 170465 1985ms
10万个3335rt,平均数为1.884063E + 295617 在3781毫秒内
因此平均运行时间约为0.1-0.5 ms,虽然不算太好,但是可以接受。
它具有以下额外功能:现在可以使用浮点数执行幂运算,仅使用整数即可,并具有以下功能:
public static BigDecimal pow(BigDecimal bd, float power) {
int rt = (int) (1D / ( power - ((long)power) )); // 1 divided by decimals in power
int i = (int)power; //take the real number part of power
return bd.pow(i,MathContext.DECIMAL64).multiply(nrt(bd,rt),MathContext.DECIMAL64);
}
答案 11 :(得分:-2)
BigDecimal.valueOf(Math.sqrt(myBigDecimal.doubleValue()));