java大数据计算遇到的科学计数显示问题及运算时精度丢失问题分析解决

大数据计算遇到的科学计数问题及运算精度丢失问题

在《Effective Java》这本书中就给出了一个解决方法。该书中也指出,floatdouble只能用来做科学计算或者是工程计算,在商业计算等精确计算中,我们要用java.math.BigDecimal

对于数据的计算可以用java.math.BigDecimal类的原生方法加减乘除都包括了

我这里有个工具类可以借鉴参考一下:

package com.dm.springboot.utils;

import java.math.BigDecimal;
import java.text.NumberFormat;

/**
 * 
 * 计算
 *
 */
public class MathUtils {
	// 默认除法运算精度
	private static final int DEF_DIV_SCALE = 10;
	// 加
	public static double add(double d1, double d2) {

		if (Double.isNaN(d1) || Double.isNaN(d2)) {
			return Double.NaN;
		}

		BigDecimal big1 = BigDecimal.valueOf(d1);
		BigDecimal big2 = BigDecimal.valueOf(d2);

		return big1.add(big2).doubleValue();
	}

	// 减
	public static double subtract(double d1, double d2) {
		if (Double.isNaN(d1) || Double.isNaN(d2)) {
			return Double.NaN;
		}

		BigDecimal big1 = BigDecimal.valueOf(d1);
		BigDecimal big2 = BigDecimal.valueOf(d2);

		return big1.subtract(big2).doubleValue();
	}

	/**
	 * 减法运算 1.NaN - NaN = NaN 2.NaN - v = NaN 3.v - NaN = v 4.v1-v2 = v3
	 * 
	 * @param v1
	 *            被减数
	 * @param v2
	 *            减数
	 * @param flag
	 * @return
	 */
	public static double subtract(double v1, double v2, boolean flag) {
		if (Double.isNaN(v1)) {
			return Double.NaN;
		} else if (Double.isNaN(v2)) {
			return v1;
		}

		BigDecimal b1 = new BigDecimal(Double.toString(v1));

		BigDecimal b2 = new BigDecimal(Double.toString(v2));

		return b1.subtract(b2).doubleValue();

	}

	/**
	 * 
	 * 提供精确的乘法运算。
	 * 
	 * @param v1
	 *            被乘数
	 * 
	 * @param v2
	 *            乘数
	 * 
	 * @return 两个参数的积
	 * 
	 */
	public static double mul(double v1, double v2) {
		if (Double.isNaN(v1) || Double.isNaN(v2)) {
			return Double.NaN;
		}

		BigDecimal b1 = new BigDecimal(Double.toString(v1));

		BigDecimal b2 = new BigDecimal(Double.toString(v2));

		return b1.multiply(b2).doubleValue();

	}

	/**
	 * 
	 * 提供精确的连乘运算。
	 * 
	 * @param inputs
	 *            连乘参数
	 * 
	 * @return 积
	 * 
	 */
	public static double mul(double[] inputs) {

		double res = 0.0;
		for (int i = 0; i < inputs.length - 1; i++) {
			double v1 = 0.0;
			double v2 = 0.0;
			if (i == 0)
				v1 = inputs[i];
			else
				v1 = res;
			v2 = inputs[i + 1];

			if (Double.isNaN(v1) || Double.isNaN(v2)) {
				return Double.NaN;
			}

			BigDecimal b1 = new BigDecimal(Double.toString(v1));

			BigDecimal b2 = new BigDecimal(Double.toString(v2));

			res = b1.multiply(b2).doubleValue();
		}
		return res;

	}

	/**
	 * 
	 * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
	 * 
	 * 小数点以后10位,以后的数字四舍五入。
	 * 
	 * @param v1
	 *            被除数
	 * 
	 * @param v2
	 *            除数
	 * 
	 * @return 两个参数的商
	 * 
	 */
	public static double div(double v1, double v2) {
		if (Double.isNaN(v1) || Double.isNaN(v2)) {
			return Double.NaN;
		}

		return div(v1, v2, DEF_DIV_SCALE);
		/*BigDecimal b1 = new BigDecimal(v1);

		BigDecimal b2 = new BigDecimal(v2);

		return b1.divide(b2, BigDecimal.ROUND_HALF_UP).doubleValue();*/

	}

	/**
	 * 
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
	 * 
	 * 定精度,以后的数字四舍五入。
	 * 
	 * @param v1
	 *            被除数
	 * 
	 * @param v2
	 *            除数
	 * 
	 * @param scale
	 *            表示表示需要精确到小数点以后几位。
	 * 
	 * @return 两个参数的商
	 * 
	 */
	public static double div(double v1, double v2, int scale) {
		if (Double.isNaN(v1) || Double.isNaN(v2)) {
			return Double.NaN;
		}

		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}

		BigDecimal b1 = new BigDecimal(Double.toString(v1));

		BigDecimal b2 = new BigDecimal(Double.toString(v2));

		return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();

	}

	/**
	 * 
	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
	 * 
	 * 定精度,以后的数字四舍五入。
	 * 
	 * @param inputs
	 *            连除参数,必须保证顺序
	 * 
	 * @param scale
	 *            表示表示需要精确到小数点以后几位。
	 * 
	 * @return 参数的商
	 * 
	 * @author Add by Huang Ye
	 * 
	 */
	public static double div(double[] inputs, int scale) {
		if (scale < 0) {
			throw new IllegalArgumentException("The scale must be a positive integer or zero");
		}
		double res = 0.0;
		for (int i = 0; i < inputs.length - 1; i++) {
			double v1 = 0.0;
			double v2 = 0.0;
			if (i == 0)
				v1 = inputs[i];
			else
				v1 = res;
			v2 = inputs[i + 1];

			if (Double.isNaN(v1) || Double.isNaN(v2)) {
				return Double.NaN;
			}

			BigDecimal b1 = new BigDecimal(Double.toString(v1));

			BigDecimal b2 = new BigDecimal(Double.toString(v2));

			res = b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
		}
		return res;

	}

	/**
	 * 
	 * 提供精确的小数位四舍五入处理。
	 * 
	 * @param dValue
	 *            需要四舍五入的数字
	 * 
	 * @param lScale
	 *            小数点后保留几位
	 * 
	 * @return 四舍五入后的结果
	 * 
	 */
	public static double round(double dValue, int lScale) {
		// 负数,则装化为正数后进行四舍五入
		boolean bFlag = false;
		if (dValue < 0) {
			bFlag = true;
			dValue = -dValue;
		}
		long lPrecision = 10000; // 精度值,为了避免double型出现偏差,增加校验位
		long l45Value = lPrecision / 2 - 1; // 四舍五入的判断值
		long lLength = 1; // 乘的数字
		for (int i = 0; i < lScale; i++) {
			lLength = lLength * 10; // 比如保留2位,乘以100
		}
		long lValue = (long) dValue; // 值的整数部分
		long lValue1 = (long) ((dValue - lValue) * lLength); // 乘以保留位数
		long lValue2 = (long) ((dValue - lValue) * lLength * lPrecision); //
		long lLastValue = lValue2 - (lValue2 / lPrecision) * lPrecision;
		if (lLastValue >= l45Value) {
			lValue1++;
		}
		dValue = lValue + (double) lValue1 / lLength; // 四舍五入后的值
		if (bFlag) {
			dValue = -dValue;
		}
		BigDecimal bd = BigDecimal.valueOf(dValue);
		bd = bd.setScale(lScale, BigDecimal.ROUND_HALF_UP);
		return bd.doubleValue();
	}

	/**
	 * 
	 * 提供两个数的比较 by Huang Ye
	 * 
	 * @param d1
	 *            需要比较的数字1
	 * 
	 * @param d2
	 *            需要比较的数字2
	 * 
	 * @param scale
	 *            与之比较的数字的小数位数
	 * 
	 * @return 是否相等
	 * 
	 */
	public static boolean equals(double d1, double d2, int scale) {
		if (Double.isNaN(d1) || Double.isNaN(d2)) {
			return false;
		}

		double difference = Math.abs(d1 - d2);
		double minNum = Math.pow(10, (-1) * scale);
		if (difference < minNum)
			return true;
		else
			return false;
	}

	/**
	 * 四舍五入保留两位小数
	 * 
	 * @param number
	 * @return
	 */
	public static Double to2dec(Number number) {
		NumberFormat format = NumberFormat.getInstance();

		format.setMaximumFractionDigits(2);
		format.setGroupingUsed(false);

		return Double.valueOf(format.format(number.doubleValue() + 1.0e-5));// 防止0.005被舍掉

	}
	/**
	 * 判断是否为数字或者小数
	 *
	 * @param str
	 * @return
	 */
	public static boolean isNumeric(String str){
		/*String reg = "\\d+(\\.\\d+)?";
		return str.matches(reg);*/
		if(str.indexOf("E")>0){
			//科学计数法或者字符串
			try {
				String  bigdecimal= new BigDecimal(str).toString();
			} catch (Exception e) {
				return false;//异常 说明包含非数字。
			}
			return true;
		}else{
			return false;
		}
	}
}

double类型数据加减乘除操作时精度丢失的问题

比方说:

这就需要先把double转换为字符串然后在作为BigDecimal(String val)构造函数的参数。转换为BigDecimal对象之后再进行加减乘除操作,这样精度就不会出现问题了。这也是为什么有关金钱数据存储都使用BigDecimal

例如:

public static double addDouble(double m1, double m2) {

        BigDecimal p1 = new BigDecimal(Double.toString(m1));

        BigDecimal p2 = new BigDecimal(Double.toString(m2));

        return p1.add(p2).doubleValue();

}

举例:

 

我们可以看出不管是DecimalFormat,还是Double.toString(),还是String.valueOf(),都最多只是精确到小数点后21位。还是会出现精度的丢失,相比与小数点后59位最大数,还是会丢失一多半的精度。

所以,如果我们计算BigDecimal数据,最好还是使用BigDecimal原生的计算方法。

但是大多数情况,我们都是使用Double保存数据,所以计算时,要想最大限度的保留精度,最好指定精度为59。59位是BigDecimal能够保留的小数点后最大位数。

例如:

public static double div(double v1, double v2) {

    if (Double.isNaN(v1) || Double.isNaN(v2)) {

        return Double.NaN;

    }

    BigDecimal b1 = new BigDecimal(Double.toString(v1));



    BigDecimal b2 = new BigDecimal(Double.toString(v2));

    BigDecimal result=b1.divide(b2,59,BigDecimal.ROUND_HALF_UP);

    System.out.println("MathUtilPlus,result:"+result);

    return Double.parseDouble(String.valueOf(result));



}

 

 

避免出现科学计数法:

public class testNumber {

    public static void main(String[] args) {

        Double s =1.00000555555555555555567120378728734269518301718764834933811;

        System.out.println("第一种方法:"+NonScientificNotation1(s));

        System.out.println("第二种方法:"+NonScientificNotation2(s,59));

    }

    /**

    * @Description: 使用BigDecimal处理

    * @Param: [num 要转的科学数字]

    */

    public static String NonScientificNotation1(Double num)

    {

        String result=new BigDecimal(num.toString()).toString();

        return result;

    }

    /**

    * @Description: 使用DecimalFormat处理

    * @Param: [num 要转的科学数字, scale 要保留小数点后的位数]

    */

    public static String NonScientificNotation2(Double num,int scale)

    {

        DecimalFormat df = new DecimalFormat("0.0");

        //设置a的小数部分允许的最大数字数

        df.setMaximumFractionDigits(scale);

        String result=df.format(num);

        return result;

    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dmlcq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值