引子

Effective Java 2nd Edition 第48条建议:如果需要精确的答案,请避免使用float和doble。float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制制浮点运算(binary floating-point arithmetic),这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,他们并没有提供完全精确的结果,所以不应该被用于需要精确结果的场合。float和double类型尤其不适合用于货币计算,因为要让float或double精确的表示0.1(或者10的任何其他负数次方值)是不可能的。解决这个问题的正确办法是使用BigDecimal、int或long进行货币计算。

1. float和double为何是近似计算

float和double无法精确计算是由他们的设计决定的。详细信息请参考下面这篇博文:

java float double精度为什么会丢失?浅谈java的浮点数精度问题

2. 使用long实现精确计算

本例子实现了使用long类型来完成浮点数的精确的加减乘,由于除法本身很多是不能整除的,所以对除法的精确计算没有想到好的方法。

由于long的表示范围为 -2^63 —– 2^63 - 1,即十进制的: -9223372036854775808 —–9223372036854775807

所以对于使用long完成精确计算必须保证计算结果及其中间结果不超过long的范围。对于比较大的数,使用BigDecimal靠谱。

上代码:

package effectivejava.chapter8;

import java.math.BigDecimal;

/**
 *
 * 本例子实现了使用long类型来完成浮点数的精确的加减乘,由于除法本身很多是不能整除的,所以对除法的精确计算没有想到好的方法。
 * 由于long的表示范围为-2^63 ----- 2^63 - 1,即十进制的: -9223372036854775808-----9223372036854775807
 * 所以对于使用long完成精确计算必须保证计算结果及其中间结果不超过long的范围。对于比较大的数,使用BigDecimal靠谱。
 */
public class ExactComputationByLong {

    /**
     * 1. 获取两个小数的右移小数点之后的long值及移动位数; 2. 统一移动位数为两个之中大的那个; 3. 相加; 4. 将小数点左移到相加的数中。
     */
    public static String add(String strA, String strB) {
        ExactLong exactA = new ExactLong(strA);
        ExactLong exactB = new ExactLong(strB);
        int maxScale = getMaxScale(exactA, exactB);
        changeSacle(exactA, maxScale);
        changeSacle(exactB, maxScale);
        long addNum = exactA.getData() + exactB.getData();
        String resultInit = getDataFromLong(addNum, maxScale);
        return getResultWithZero(resultInit);
    }

    /**
     * 过程与加法相同。
     */
    public static String subtract(String strA, String strB) {
        ExactLong exactA = new ExactLong(strA);
        ExactLong exactB = new ExactLong(strB);
        int maxScale = getMaxScale(exactA, exactB);
        changeSacle(exactA, maxScale);
        changeSacle(exactB, maxScale);
        long subNum = exactA.getData() - exactB.getData();
        String resultInit = getDataFromLong(subNum, maxScale);
        return getResultWithZero(resultInit);

    }

    /**
     * 过程与加法相似,唯一不同是移动位数为maxScale * 2。
     */
    public static String multiply(String strA, String strB) {
        ExactLong exactA = new ExactLong(strA);
        ExactLong exactB = new ExactLong(strB);
        if (exactA.getData() == 0 || exactB.getData() == 0) {
            return "0";
        }
        int maxScale = getMaxScale(exactA, exactB);
        changeSacle(exactA, maxScale);
        changeSacle(exactB, maxScale);
        long subNum = exactA.getData() * exactB.getData();
        String resultInit = getDataFromLong(subNum, maxScale * 2);
        return getResultWithZero(resultInit);
    }

    /**
     * 对于除法,我没有想到好的方法来精确计算。而且除法本身有很多不是整除的,所以除法一般都要确定一个精度,然后再计算。水平有限,这里就用BigDecimal的一个实现代替吧☺
     */
    public static String divide(String strA, String strB) {
        return new BigDecimal(strA).divide(new BigDecimal(strB), BigDecimal.ROUND_DOWN).toPlainString();
    }

    private static void changeSacle(ExactLong exactData, int maxScale) {
        if (maxScale != exactData.getScale()) {
            exactData.setData((long) (exactData.getData() * Math.pow(10, maxScale - exactData.getScale())));
            exactData.setScale(maxScale);
        }
    }

    private static int getMaxScale(ExactLong exactA, ExactLong exactB) {
        int maxScale = exactA.getScale() > exactB.getScale()?exactA.getScale():exactB.getScale();
        return maxScale;
    }

    /**
     * 将dataL小数点左移scale位
     */
    private static String getDataFromLong(long dataL, int scale) {
        String dataStr = dataL + "";
        if (scale < 0) {
            throw new IllegalArgumentException("The degree must greater than 0!!");
        }
        if (scale == 0) {
            return dataStr;
        }
        if (dataStr.length() > scale) {
            return dataStr.substring(0, dataStr.length() - scale) + "." + dataStr.substring(dataStr.length() - scale);
        } else {
            StringBuilder dataSB = new StringBuilder("0.");
            for (int i = 0;i < scale - dataStr.length();i++) {
                dataSB.append("0");
            }
            dataSB.append(dataStr);
            return dataSB.toString();
        }
    }

    /**
     * 去掉小数点之后多余的0
     */
    private static String getResultWithZero(String resultInit) {
        int index = resultInit.length();
        // 去处小数位最后可能的多个0
        for (int i = resultInit.length() - 1;i >= 0;i--) {
            if (resultInit.charAt(i) != '0') {
                index = i + 1;
                break;
            }
        }
        return resultInit.substring(0, index);
    }

    public static void main(String[] args) {
        String strA = "0.001234560";
        String strB = "10.2345";
        String strAdd = add(strA, strB);
        System.out.println("Add:\n" + strA + "\n" + strB + "\n" + strAdd);
        String strSub = subtract(strA, strB);
        System.out.println("Sub:\n" + strA + "\n" + strB + "\n" + strSub);
        String strMult = multiply(strA, strB);
        System.out.println("Mult:\n" + strA + "\n" + strB + "\n" + strMult);
        String sttDiv = divide(strA, strB);
        System.out.println("Div:\n" + strA + "\n" + strB + "\n" + sttDiv);
    }

}

/**
 * 将一个字符串表示的浮点数用long表示,scale表示小数点右移的位数。
 */
class ExactLong {
    private long data;
    private int scale;

    public ExactLong(String dataStr) {
        if (dataStr.indexOf(".") == -1) {// 如123456
            data = Long.parseLong(dataStr);
            scale = 0;
            return;
        }
        if (dataStr.indexOf(".") != dataStr.lastIndexOf(".")) {// 如123.456.7
            throw new IllegalArgumentException(dataStr + " can not cast to long!");
        } else {// 如12345.678
            scale = dataStr.length() - dataStr.indexOf(".") - 1;
            long beforePoint = Long.parseLong(dataStr.substring(0, dataStr.indexOf(".")));// 小数点之前的数
            long afterPoint = Long.parseLong(dataStr.substring(dataStr.indexOf(".") + 1, dataStr.length()));// 小数点之后的数
            data = (long) (beforePoint * Math.pow(10, scale) + afterPoint);
        }
    }

    public long getData() {
        return data;
    }

    public void setData(long data) {
        this.data = data;
    }

    public int getScale() {
        return scale;
    }

    public void setScale(int scale) {
        this.scale = scale;
    }

}

Java中使用long类型实现精确的四则运算的更多相关文章

  1. Java中的Bigdecimal类型运算

    Java中的Bigdecimal类型运算 双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.Java在java.math包中提 供的API类BigD ...

  2. 理解Java中的字符串类型

    1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和w ...

  3. Java中的枚举类型详解

    枚举类型介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义 ...

  4. 关于java中的事件类型

    java中的Date是为了证明:天才的程序员也会犯错: java中的Calendar是为了证明:普通的程序员也会犯错. ———————————————————— stackoverflow上大部分都推 ...

  5. Java中的集合类型的继承关系图

    Java中的集合类型的继承关系图

  6. java中,字符串类型的时间数据怎样转换成date类型。

    将字符串类型的时间转换成date类型可以使用SimpleDateFormat来转换,具体方法如下:1.定义一个字符串类型的时间:2.创建一个SimpleDateFormat对象并设置格式:3.最后使用 ...

  7. 1 Java中的时间类型

    总结:sql中的时间转 util的时间直接赋值即可:反过来,必须先吧util下的时间转换成毫秒,再通过sql的构造器生成sql的时间格式. 1 Java中的时间类型 java.sql包下给出三个与数据 ...

  8. 详解java中的byte类型

    Java也提供了一个byte数据类型,并且是基本类型.java byte是做为最小的数字来处理的,因此它的值域被定义为-128~127,也就是signed byte.下面这篇文章主要给大家介绍了关于j ...

  9. MySql数据库类型bit等与JAVA中的对应类型【布尔类型怎么存】

    用char(1):可以表示字符或者数字,但是不能直接计算同列的值.存储消耗1个字节 用tinyint:只能表示数字,可以直接计算,存储消耗2个字节 用bit: 只能表示0或1,不能计算,存储消耗小于等 ...

随机推荐

  1. UVA-10037 Bridge---过河问题进阶版(贪心)

    题目链接: https://vjudge.net/problem/UVA-10037 题目大意: N个人夜里过河,总共只有一盏灯,每次最多过两个人,然后需要有人将灯送回 才能继续过人,每个人过桥都需要 ...

  2. angularjs购物车练习

    本文是一个简单的购物车练习,功能包括增加.减少某商品的数量,从而影响该商品的购买总价以及所有商品的购买总价:从购物车内移除一项商品:清空购物车. 页面效果如图: 若使用js或jQuery来实现这个页面 ...

  3. 解决:My97DatePicker 日期插件引用在PHP文件中maxDate和minDate控制失效问题

    开发环境: 语言:PHP 框架:ThinkPHP 问题:在引用插件My97DatePicker时,想实现:开始日期不能大于结束日期,结束时间不能小于开始时间 步骤一.查看文档官方文档http://ww ...

  4. 有些ES6方法极简,但是性能不够好

    So,也许你觉得ES6让你视野大开,但是并不是性能也能跟得上~ 首先,让我们先来一个简单的性能测试: 数组去重 es5写法: function delSame(arr){ var n = []; ; ...

  5. MyBatis基础学习笔记--摘录

    1.MyBatis是什么? MyBatis源自于IBatis,是一个持久层框架,封装了jdbc操作数据库的过程,使得开发者只用关心sql语句,无需关心驱动加载.连接,创建statement,手动设置参 ...

  6. 机器学习基石:10 Logistic Regression

    线性分类中的是非题------>概率题, 设置概率阈值后,大于等于该值的为O,小于改值的为X.------>逻辑回归. O为1,X为0: 逻辑回归假设: 逻辑函数/S型函数:光滑,单调, ...

  7. 各种电脑进入BIOS快捷键

    组装机主板 品牌笔记本 品牌台式机 主板品牌 启动按键 笔记本品牌 启动按键 台式机品牌 启动按键 华硕主板 F8 联想笔记本 F12 联想台式机 F12 技嘉主板 F12 宏基笔记本 F12 惠普台 ...

  8. Java IO(四)

    在文件操作流中,输入输出的目标都是文件,但是有时候,我们并不需要写入文件,只是需要中转一下而已,这样就会显得很麻烦,所以我们就可以使用内存操作流.在内存操作流中,输入输出目标都是内存. 内存输出流:B ...

  9. [HNOI 2001]软件开发

    Description 某软件公司正在规划一项n天的软件开发计划,根据开发计划第i天需要ni个软件开发人员,为了提高软件开发人员的效率,公司给软件人员提供了很多的服务,其中一项服务就是要为每个开发人员 ...

  10. [USACO 08DEC]Secret Message

    Description Bessie is leading the cows in an attempt to escape! To do this, the cows are sending sec ...