和钱有关的那些事-java中的数字精确表示

前言

        一日,一个外国人来到我大中华,操着一口纯正无比的外国腔。然而,我们都听不懂啊!这个地方出现了一个问题,那就是语言不通。实际上,在数字的表示上也存在着类似的问题!

例子

        大家看看下面的代码


public static void main(String[] args) {
        double s = 1D;
        System.out.println(s-0.9);
}

        实际上,上面的代码带有一定的误导性,如果没有被坑过的话,很有可能会说这段代码的结果是0.1。但是,如果结果那么简单,我还会贴出来吗?实际上的结果如下:


1


        接下来,大家应该知道本篇文章的一个主旨了!

和钱有关的那些事

        好吧,事到如今,我就来给大家解释一下为何会出现上述的结果!首先,我们知道我们的代码最终都会变成二进制在机器的世界中自由的执行,所以这里会出现一个问题,那就是在十进制能表示为整数的在二进制那里是不一定的。也就是说,实际上二进制和十进制在相互转换的过程中存在着精度丢失的问题。这也就是说,在涉及到钱的时候,请千万不要用double和float等类型。否则比如有一天你的商品价格成了8.0000000000000001这样的数字的时候那是一件多麽尴尬的事情。
        好了,废话不多说,现在我们来说说有什么正确的姿势来进行钱的计算。

姿势一、BigDecimal类

        这个类是java中的一个专门用来精确计算的类,其提供的功能十分强大。可以从各种数据类型转换为BigDecimal的实体,具体到有哪些的数据类型,我们简单看下它的构造方法!

    /**
     * Translates a {@code double} into a {@code BigDecimal} which
     * is the exact decimal representation of the {@code double}'s
     * binary floating-point value.  The scale of the returned
     * {@code BigDecimal} is the smallest value such that
     * <tt>(10<sup>scale</sup> &times; val)</tt> is an integer.
     * <p>
     * <b>Notes:</b>
     * <ol>
     * <li>
     * The results of this constructor can be somewhat unpredictable.
     * One might assume that writing {@code new BigDecimal(0.1)} in
     * Java creates a {@code BigDecimal} which is exactly equal to
     * 0.1 (an unscaled value of 1, with a scale of 1), but it is
     * actually equal to
     * 0.1000000000000000055511151231257827021181583404541015625.
     * This is because 0.1 cannot be represented exactly as a
     * {@code double} (or, for that matter, as a binary fraction of
     * any finite length).  Thus, the value that is being passed
     * <i>in</i> to the constructor is not exactly equal to 0.1,
     * appearances notwithstanding.
     *
     * <li>
     * The {@code String} constructor, on the other hand, is
     * perfectly predictable: writing {@code new BigDecimal("0.1")}
     * creates a {@code BigDecimal} which is <i>exactly</i> equal to
     * 0.1, as one would expect.  Therefore, it is generally
     * recommended that the {@linkplain #BigDecimal(String)
     * <tt>String</tt> constructor} be used in preference to this one.
     *
     * <li>
     * When a {@code double} must be used as a source for a
     * {@code BigDecimal}, note that this constructor provides an
     * exact conversion; it does not give the same result as
     * converting the {@code double} to a {@code String} using the
     * {@link Double#toString(double)} method and then using the
     * {@link #BigDecimal(String)} constructor.  To get that result,
     * use the {@code static} {@link #valueOf(double)} method.
     * </ol>
     *
     * @param val {@code double} value to be converted to
     *        {@code BigDecimal}.
     * @throws NumberFormatException if {@code val} is infinite or NaN.
     */
    public BigDecimal(double val) {
        if (Double.isInfinite(val) || Double.isNaN(val))
            throw new NumberFormatException("Infinite or NaN");

        // Translate the double into sign, exponent and significand, according
        // to the formulae in JLS, Section 20.10.22.
        long valBits = Double.doubleToLongBits(val);
        int sign = ((valBits >> 63)==0 ? 1 : -1);
        int exponent = (int) ((valBits >> 52) & 0x7ffL);
        long significand = (exponent==0 ? (valBits & ((1L<<52) - 1)) << 1
                            : (valBits & ((1L<<52) - 1)) | (1L<<52));
        exponent -= 1075;
        // At this point, val == sign * significand * 2**exponent.

        /*
         * Special case zero to supress nonterminating normalization
         * and bogus scale calculation.
         */
        if (significand == 0) {
            intVal = BigInteger.ZERO;
            intCompact = 0;
            precision = 1;
            return;
        }

        // Normalize
        while((significand & 1) == 0) {    //  i.e., significand is even
            significand >>= 1;
            exponent++;
        }

        // Calculate intVal and scale
        long s = sign * significand;
        BigInteger b;
        if (exponent < 0) {
            b = BigInteger.valueOf(5).pow(-exponent).multiply(s);
            scale = -exponent;
        } else if (exponent > 0) {
            b = BigInteger.valueOf(2).pow(exponent).multiply(s);
        } else {
            b = BigInteger.valueOf(s);
        }
        intCompact = compactValFor(b);
        intVal = (intCompact != INFLATED) ? null : b;
    }

        上述是从一个double的值得到一个BigDecimal的实体。然而,我想问一句,你在传给BigDecimal的构造方法参数的时候能保证你这个double值是一个正常的值吗?如果不能也不想那么麻烦,请传入String类型,确保万无一失。
        我们还经常会遇到这样的问题,那就是传入的是一个不规则的小数,可我们就是要拿到一个规则的,这个时候,BIgdecimal为我们提供了一个设置精度的方法。那就是

public BigDecimal setScale(int newScale, RoundingMode roundingMode) {
        return setScale(newScale, roundingMode.oldMode);
}

        这个方法可以设置传入的值构造的BigdeCimal的精度。哈哈,是不是很强大,当然,使用这个类,要付出的代价就是加减乘除等基本的操作也开始变得复杂了。当然,要享受一个东西的好,有时候就要付出点什么!
        具体的BigDecimal的使用方法在这里就不做赘述了。实际上,网上的资源太多了。

姿势二、把钱存成分

        大家看看自己支付宝里面的存款,不对,是余额。是不是精确到小数点后两位啊!那我们就把他扩大一百倍,然后存起来,这样的话就万无一失。
        当然,这样做要付出的代价就是小数点要自己算,小心算错啊。万一退款多退了10倍就不太好了!哈哈

总结

        终于到了最后,本篇文章我们讨论了精确计算。详细的说明了其中的两种方法,各有好处吧!同时也为我们敲醒了警钟,那就是在面对钱的一定要注意注意在注意啊!小心使得万年船吗!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值