资讯专栏INFORMATION COLUMN

高精度数学运算

liaosilzu2007 / 699人阅读

摘要:使用,保证精度的同时,能精准的进行四舍六入计算。类精确的数学运算使用来实现精准度因为精度的原因构造方法的结果有一定的不可预知性,例如因此建议使用。算法规则四舍六入五考虑,五后非零就进一,五后皆零看奇偶,五前为偶应舍去,五前为奇要进一。

四舍六入计算
算法规则:
四舍六入五考虑,
五后非零就进一,
五后皆零看奇偶,
五前为偶应舍去,
五前为奇要进一。

使用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));
  }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69515.html

相关文章

  • 系统的讲解 - PHP 浮点数精度运算

    摘要:浮点数类型包括单精度浮点数和双精度浮点数。小结通过浮点数精度的问题,了解到浮点数的小数用二进制的表示。以后,在使用浮点数运算的时候,一定要慎之又慎,细节决定成败。 概述 记录下,工作中遇到的坑 ... 关于 PHP 浮点数运算,特别是金融行业、电子商务订单管理、数据报表等相关业务,利用浮点数进行加减乘除时,稍不留神运算结果就会出现偏差,轻则损失几十万,重则会有信誉损失,甚至吃上官司,我...

    makeFoxPlay 评论0 收藏0
  • PHP处理数学精度

    摘要:后来我看了下,确实有这么一个扩展库,处理任意精度数字,对于任意精度的数学,提供了支持用字符串表示的任意大小和精度的数字的二进制计算。 用编程语言做计算,很多时候浮点数精度都是困扰过我的问题,即便是刚学PHP的新手也会在群里问为什么我的计算结果明显不对,而我们总是老态龙钟的丢出一句浮点数计算都存在精度问题,并没有提出过什么实质性的改善。比如下面的计算 0.57*100: zhgxun-p...

    chaos_G 评论0 收藏0
  • PHP变量类型

    摘要:获取变量的类型,使用函数。要检验某个类型,可以使用函数,如是在整形是在浮点型是在字符串如果需要将一个变量强制转换为某类型,可以对其使用强制转换或者函数。自起,此限制仅对包含变量时有效。 简介 PHP支持9种原始数据类型。 4种标量类型: boolean布尔型 integer 整形 float 浮点型(也称作double) string 字符串 3种复合类型: array...

    selfimpr 评论0 收藏0
  • Python基础之(一)基本数据类型

    摘要:但是在转化中,浮点数转化为二进制后,不会精确等于十进制的。一般情况下,只要简单地将最终显示的结果用四舍五入到所期望的十进制位数,就会得到期望的最终结果。四舍五入内建函数。在中的第二个数,表示要保留的小数位数,返回值是一个四舍五入之后的数值。 数字 基本类型 首先,进入Python交互模式中: //整数 >>> 3 3 //长整数 >>> 3333333333333333333333...

    yagami 评论0 收藏0
  • 如何解决0.1 +0.2===0.30000000000000004类问题

    摘要:方法使用定点表示法来格式化一个数,会对结果进行四舍五入。该数值在必要时进行四舍五入,另外在必要时会用来填充小数部分,以便小数部分有指定的位数。如果数值大于,该方法会简单调用并返回一个指数记数法格式的字符串。在环境中,只能是之间,测试版本为。 showImg(https://segmentfault.com/img/remote/1460000011913134?w=768&h=521)...

    yuanzhanghu 评论0 收藏0

发表评论

0条评论

liaosilzu2007

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<