Any neat way to limit significant figures with BigDecimal

泪湿孤枕 提交于 2019-12-14 03:45:45

问题


I want to round a Java BigDecimal to a certain number of significant digits (NOT decimal places), e.g. to 4 digits:

12.3456 => 12.35
123.456 => 123.5
123456 => 123500

etc. The basic problem is how to find the order of magnitude of the BigDecimal, so I can then decide how many place to use after the decimal point.

All I can think of is some horrible loop, dividing by 10 until the result is <1, I am hoping there is a better way.

BTW, the number might be very big (or very small) so I can't convert it to double to use Log on it.


回答1:


The easierst solution is:

  int newScale = 4-bd.precision()+bd.scale();
  BigDecimal bd2 = bd1.setScale(newScale, RoundingMode.HALF_UP);

No String conversion is necessary, it is based purely on BigDecimal arithmetic and therefore as efficient as possible, you can choose the RoundingMode and it is small. If the output should be a String, simply append .toPlainString().




回答2:


Why not just use round(MathContext)?

BigDecimal value = BigDecimal.valueOf(123456);
BigDecimal wantedValue = value.round(new MathContext(4, RoundingMode.HALF_UP));



回答3:


You can use the following lines:

int digitsRemain = 4;

BigDecimal bd = new BigDecimal("12.3456");
int power = bd.precision() - digitsRemain;
BigDecimal unit = bd.ulp().scaleByPowerOfTen(power);
BigDecimal result = bd.divideToIntegralValue(unit).multiply(unit);

Note: this solution always rounds down to the last digit.




回答4:


Someone will probably come up with a better solution, but the first thing that comes to mind is chuck it in to a StringBuilder, check whether it contains a '.' and return an appropriate length substring. E.g.:

int n = 5;
StringBuilder sb = new StringBuilder();
sb.append("" + number);
if (sb.indexOf(".") > 0)
{
    n++;
}
BigDecimal result = new BigDecimal(sb.substring(0, n));



回答5:


To me this seems as simple as: Given N = 5, D = 123.456789

  1. get the string representation of the number, "123.456789"
  2. retrieve the first N-1 digits of the number, "123.4"
  3. evaluate D[N] and D[N+1], in this case "5" and "6"
  4. 6 meets the criteria for rounding up (6 > 4), therefore carry 1 and make D[N] = 5+1 = 6
  5. D post rounding is now 123.46

Order can be calculated using Math.floor(Math.log(D)).

hope this helps.




回答6:


Since BigDecimal is basically a string, perhaps this:

import java.math.BigDecimal;

public class Silly {
    public static void main( String[] args ) {
        BigDecimal value = new BigDecimal("1.23238756843723E+5");
        String valueString = value.toPlainString();
        int decimalIndex = valueString.indexOf( '.' );
        System.out.println( value + " has " +
            (decimalIndex < 0 ? valueString.length() : decimalIndex) +
            " digits to the left of the decimal" );
    }
}

Which produces this:

123238.756843723 has 6 digits to the left of the decimal



回答7:


A.H.'s answer is technically correct, but here is a more general (and easier to understand) solution:

import static org.bitbucket.cowwoc.requirements.core.Requirements.assertThat;

/**
 * @param value            a BigDecimal
 * @param desiredPrecision the desired precision of {@code value}
 * @param roundingMode     the rounding mode to use
 * @return a BigDecimal with the desired precision
 * @throws NullPointerException if any of the arguments are null
 */
public BigDecimal setPrecision(BigDecimal value, int desiredPrecision, RoundingMode roundingMode)
{
    assertThat("value", value).isNotNull();
    assertThat("roundingMode", roundingMode).isNotNull();
    int decreaseScaleBy = value.precision() - desiredPrecision;
    return value.setScale(value.scale() - decreaseScaleBy, roundingMode);
}


来源:https://stackoverflow.com/questions/7572309/any-neat-way-to-limit-significant-figures-with-bigdecimal

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!