java阶乘计算高精度_高精度数学运算

本文介绍了如何在Java中使用BigDecimal进行高精度数学运算,包括四舍六入的算法实现。通过MathUtil类提供精确的加、减、乘、除、幂和阶乘计算,优化了排列组合公式,先约分再计算以减少计算步骤。还展示了四舍六入五成双的规则,并提供了测试用例验证算法的正确性。
摘要由CSDN通过智能技术生成

四舍六入计算

算法规则:

四舍六入五考虑,

五后非零就进一,

五后皆零看奇偶,

五前为偶应舍去,

五前为奇要进一。

使用BigDecimal,保证精度的同时,能精准的进行四舍六入计算。

优化排列组合算法

关于排列组合公式,请百度。网上一大堆算法,都先计算阶乘再相除。但实际上应该先约分,一下子就节约了很多计算步骤。以排列公式来说P(n,r)=n!/(n-r)!,实际计算中就是n 乘到 n-r就可以了。组合公式就是排列算法再除以r的阶乘。

MathUtil类

import org.apache.commons.lang3.ArrayUtils;

import java.math.BigDecimal;

import java.math.RoundingMode;

/**

* 精确的数学运算

*

使用 {@link java.math.BigDecimal}来实现精准度

* 因为精度的原因BigDecimal(double val)构造方法的结果有一定的不可预知性,例如:

*

 
 

* System.out.println(new BigDecimal(0.2)); //0.200000000000000011102230246251565404236316680908203125

* System.out.println(BigDecimal.valueOf(0.2f)); //0.20000000298023224

* System.out.println(BigDecimal.valueOf(0.2d)); //0.2

* System.out.println(BigDecimal.valueOf(0.2)); //0.2

* System.out.println(new BigDecimal("0.2")); //0.2

*

*

因此建议使用new BigDecimal(String)

* @author BBF

*/

public final class MathUtil {

/**

* PI,比Math.PI多两位

*/

public static final double PI = 3.1415926535897932384626;

/**

* 默认除法运算精度

*/

private static final int DEFAULT_SCALE = 10;

private static final double NUM_ROUND = 0.5;

/**

* 运算枚举

*/

private enum MathType {

/**

* 加法

*/

ADD,

/**

* 减法

*/

SUB,

/**

* 乘法

*/

MULTI,

/**

* 除法

*/

DIV

}

private MathUtil() {

}

/**

* 转换为{@link java.math.BigDecimal}

*

为保证精度,先转成{@link java.lang.String}然后再用构造函数

* @param value 数值

* @return {@link java.math.BigDecimal}

*/

private static BigDecimal convertToBigDecimal(Number value) {

return value == null ? BigDecimal.ZERO : new BigDecimal(value.toString());

}

/**

* 提供精确的加法、减法和乘法运算

* @param type 运算法则

* @param scale 精确到小数点后几位,只在除法有效

* @param values 多个值

* @return 四则运算结果

*/

private static BigDecimal calculate(MathType type, int scale, Number[] values) {

if (ArrayUtils.isEmpty(values)) {

return BigDecimal.ZERO;

}

// 第一个数作为被加数、被减数或被乘数

Number value = values[0];

BigDecimal result = convertToBigDecimal(value);

for (int i = 1, l = values.length; i < l; i++) {

value = values[i];

if (value != null) {

switch (type) {

case ADD:

result = result.add(convertToBigDecimal(value));

break;

case SUB:

result = result.subtract(convertToBigDecimal(value));

break;

case MULTI:

result = result.multiply(convertToBigDecimal(value));

break;

case DIV:

result = result.divide(convertToBigDecimal(value), scale, RoundingMode.HALF_UP);

break;

default:

break;

}

}

}

return result;

}

/**

* 提供精确的幂运算

* @param value 底数

* @param n 指数

* @return 幂的积

*/

public static BigDecimal pow(Number value, int n) {

return convertToBigDecimal(value).pow(n);

}

/**

* 提供精确的加法运算

* @param values 多个值的字符串

* @return 和

*/

public static BigDecimal add(Number... values) {

return calculate(MathType.ADD, DEFAULT_SCALE, values);

}

/**

* 提供精确的减法运算

* @param values 多个值的字符串

* @return 差

*/

public static BigDecimal sub(Number... values) {

return calculate(MathType.SUB, DEFAULT_SCALE, values);

}

/**

* 提供精确的乘法运算

* @param values 多个值的字符串

* @return 积

*/

public static BigDecimal multi(Number... values) {

return calculate(MathType.MULTI, DEFAULT_SCALE, values);

}

/**

* 提供(相对)精确的除法运算

*

当发生除不尽的情况时,精确到小数点以后10位,以后的数字四舍五入

* @param values 多个值的字符串

* @return 商

*/

public static BigDecimal div(Number... values) {

return calculate(MathType.DIV, DEFAULT_SCALE, values);

}

/**

* 提供(相对)精确的除法运算

*

当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入

* @param scale 精确到小数点后几位,只在除法有效

* @param values 多个值的字符串

* @return 商

*/

public static BigDecimal divByScale(int scale, Number... values) {

if (scale < 0) {

throw new IllegalArgumentException("The scale must be a positive integer or zero");

}

return calculate(MathType.DIV, scale, values);

}

/**

* 四舍六入五成双算法

*

四舍六入五成双是一种比较精确比较科学的计数保留法,是一种数字修约规则。

*

 
 

* 算法规则:

* 四舍六入五考虑,

* 五后非零就进一,

* 五后皆零看奇偶,

* 五前为偶应舍去,

* 五前为奇要进一。

*

* @param value 需要科学计算的数据

* @param digit 保留的小数位

* @return 指定小数位数的数字

*/

public static BigDecimal round(Number value, int digit) {

// 小数进位,然后取整计算,再退位得到结果

BigDecimal ratio = pow(10, digit);

// 进位后的数字

BigDecimal number = multi(value, ratio);

// 获取BigDecimal整数部分,直接舍弃小数部分

long integer = number.setScale(0, RoundingMode.DOWN).longValue();

// 获取小数部分

double decimal = sub(number, integer).doubleValue();

if (decimal > NUM_ROUND) {

// 四舍六入

integer = integer + 1;

}

if (decimal == NUM_ROUND && integer % 2 != 0) {

// 五前为奇要进一

integer = integer + 1;

}

return div(integer, ratio).setScale(digit, RoundingMode.HALF_UP);

}

/**

* 计算阶乘

*

n! = n * (n-1) * ... * end

* @param n 阶乘起始

* @param end 阶乘结束

* @return 结果

*/

public static BigDecimal factorial(Number n, int end) {

int st = n.intValue();

if (st < end) {

return BigDecimal.ZERO;

}

if (st == end) {

return BigDecimal.ONE;

}

return multi(n, factorial(sub(n, 1), end));

}

/**

* 计算阶乘

*

n! = n * (n-1) * ... * 2 * 1

* @param n 阶乘起始

* @return 结果

*/

public static BigDecimal factorial(Number n) {

return factorial(n, 1);

}

/**

* 计算排列

*

P(n, r) = n!/(n-r)!

*

从n个不同的元素中,取r个不重复的元素,按次序排列

* @param n 总数

* @param r 要取出数量

* @return 排列数

*/

public static long arrangement(int n, int r) {

if (n < r) {

return 0;

}

// 对公式约分,实际上是计算了n 到 n-r的阶乘

return factorial(n, n - r).longValue();

}

/**

* 计算组合

*

C(n, r) = n!/((n-r)! * r!)

*

从n个不同的元素中,取r个不重复的元素,不考虑顺序

* @param n 总数

* @param r 要取出数量

* @return 组合数

*/

public static long combination(int n, int r) {

if (n < r) {

return 0;

}

// 组合就是排列的结果再除以r的阶乘

return div(arrangement(n, r), factorial(r)).longValue();

}

}

测试用例

import org.junit.Test;

import java.math.BigDecimal;

/**

* MathUtil测试类

* @author BBF

*/

public class MathUtilTest {

@Test

public void showBigDecimal() {

System.out.println(new BigDecimal(0.2)); //0.200000000000000011102230246251565404236316680908203125

System.out.println(BigDecimal.valueOf(0.2f)); //0.20000000298023224

System.out.println(BigDecimal.valueOf(0.2d)); //0.2

System.out.println(BigDecimal.valueOf(0.2)); //0.2

System.out.println(new BigDecimal("0.2")); //0.2

}

@Test

public void add() {

BigDecimal bigDecimal = new BigDecimal("1.91");

double ab = MathUtil.add(8, 0.1, 0.2f, 0.3d, bigDecimal).doubleValue();

System.out.println("各种类型数值相加,预期:10.51 实际:" + ab);

}

@Test

public void round() {

System.out.println("四舍六入预期:4.24 实际:" + MathUtil.round(4.245, 2).toString());

System.out.println("四舍六入预期:4.24 实际:" + MathUtil.round(4.2450, 2).toString());

System.out.println("四舍六入预期:4.25 实际:" + MathUtil.round(4.2451, 2).toString());

System.out.println("四舍六入预期:4.22 实际:" + MathUtil.round(4.2250, 2).toString());

System.out.println("四舍六入预期:1.20 实际:" + MathUtil.round(1.2050, 2).toString());

System.out.println("四舍六入预期:1.22 实际:" + MathUtil.round(1.2150, 2).toString());

System.out.println("四舍六入预期:1.22 实际:" + MathUtil.round(1.2250, 2).toString());

System.out.println("四舍六入预期:1.24 实际:" + MathUtil.round(1.2350, 2).toString());

System.out.println("四舍六入预期:1.24 实际:" + MathUtil.round(1.2450, 2).toString());

System.out.println("四舍六入预期:1.26 实际:" + MathUtil.round(1.2550, 2).toString());

System.out.println("四舍六入预期:1.26 实际:" + MathUtil.round(1.2650, 2).toString());

System.out.println("四舍六入预期:1.28 实际:" + MathUtil.round(1.2750, 2).toString());

System.out.println("四舍六入预期:1.28 实际:" + MathUtil.round(1.2850, 2).toString());

System.out.println("四舍六入预期:1.30 实际:" + MathUtil.round(1.2950, 2).toString());

}

@Test

public void factorial() {

System.out.println("阶乘10!:预期:3628800 实际:" + MathUtil.factorial(10).toString());

}

@Test

public void arrangement() {

System.out.println("排列P(10,2),预期:90 实际:" + MathUtil.arrangement(10, 2));

}

@Test

public void combination() {

System.out.println("排列C(10,2),预期:45 实际:" + MathUtil.combination(10, 2));

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值