四舍六入计算
算法规则:
四舍六入五考虑,
五后非零就进一,
五后皆零看奇偶,
五前为偶应舍去,
五前为奇要进一。
使用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));
}
}