Java计算

用二进制表示正数、负数、小数

二进制中的原码、反码、补码
对于有符号数而言:

  (1)二进制的最高位是符号位:0表示正数,1表示负数
  (2)正数的原码、反码、补码都一样;

  (3)负数的反码 =  它的原码符号位不变,其他位取反(0 ->1 ; 1->0 );

  (4)负数的补码 = 它的反码 +1;

  (5)0的反码、补码都是0;

  (6)在计算机运算的时候,都是以补码的方式来运算的;

例如:1-2,在计算机中运算如下:
在计算机中减运算其实是作为加运算来操作的,所以,1-2 = 1 + ( -2 )
第一步:把 1补码找出来(因为正数的原码、反码、补码都一样,所以我们可通过原码直接获取补码):

         1的补码:

         00000000   00000000   00000000   00000001

       第二步:把-2的原码找出来:

         -2的原码:

         10000000   00000000   00000000   00000010

       第三步:把-2的反码找出来:

         -2的反码:

         11111111     11111111     11111111     11111101

       第三步:把-2的补码找出来:

         -2的补码:

         11111111     11111111     11111111     11111110

       第四步:1的补码与-2的补码相加:

          00000000   00000000   00000000   00000001

          +
          11111111     11111111     11111111      11111110

          =

          11111111     11111111     11111111      11111111

       第五步:将计算结果的补码转换为原码,反其道而行之即可(如果想将二进制转换为十进制,必须得到二进制的原码)
          补码:11111111     11111111     11111111      11111111

          =

          反码:11111111     11111111     11111111      11111110

          =

          原码:10000000  00000000   00000000    00000001

      第六步:将计算结果的二进制原码 转换 为十进制

          二进制原码:10000000  00000000   00000000    00000001  =  1*2^0 =  -1
计算机中为什么要用补码来计算?

我们希望只设计加法运算器,不用减法运算器,我们希望找到一种方案,采用这种方案做加运算 1 + ( -1 ) ,两个数可以直接根据二进制的加法规则做运算,得到0,而不必做减法。
用 0000 0000表示0是很自然的想法,用 0000 0001到 0111 1111表示1到127的正数,也是自然的想法,此时,最高位的0可以做符号标识,也可以看成普通的二进制位。
现在问题是:怎么表示-1呢?
我们做一次逆向思维,0000 0001加上什么样的二进制数可以得到0000 0000?即:从右向左思考,加数的最右边的最低位必须是1,根据二进制加法规则:1+1=0,进位为1。再考虑次低位,加数的次低位也必须是1,然后加上1得0进一位,…依次类推,加数的8为都必须是1,才可以得到8个0。问题是最后产生一个进位,即:0000 0001 + (1111 1111)= 1 0000 0000
这在数学上是不可接受的,但是在计算机中去刚好合适,因为在设计中,每个数的长度是确定的,所以无论结果最后是多少,都只保留8位,多余的位会被丢弃。因此,我们可以将 1111 1111来表示-1,下面就是采用一种方式来合理的将-1怎么变成 1111 1111这种形式。
带符号整数有原码、反码、补码等几种编码方式。原码即直接将真值转换為其相应的二进制形式,而反码和补码是对原码进行某种转换编码方式。正整数的原码、反码和补码都一样,负数的反码是对原码的除符号位外的其他位进行取反后的结果(取反即如果该位為0则变為1而该位為1则变為0操作)而补码是先求原码的反码,然后在反码的末尾位加1后得到结果,即补码是反码+1
补码就是最方便的方式。它的便利体现在,所有的加法运算可以使用同一种电路完成。
以-8作为例子。假定有两种表示方法。一种是直觉表示法,即10001000;另一种是2的补码表示法,即11111000。请问哪一种表示法在加法运算中更方便?
随便写一个计算式,16 + (-8) = ?
16的二进制表示是 00010000,所以用直觉表示法,加法就要写成:
 00010000
+10001000
---------
 10011000
可以看到,如果按照正常的加法规则,就会得到10011000的结果,转成十进制就是-24。显然,这是错误的答案。也就是说,在这种情况下,正常的加法规则不适用于正数与负数的加法,因此必须制定两套运算规则,一套用于正数加正数,还有一套用于正数加负数。从电路上说,就是必须为加法运算做两种电路。
现在,再来看2的补码表示法。
 00010000
+11111000
---------
100001000
可以看到,按照正常的加法规则,得到的结果是100001000。注意,这是一个9位的二进制数。我们已经假定这是一台8位机,因此最高的第9位是一个溢出位,会被自动舍去。所以,结果就变成了00001000,转成十进制正好是8,也就是16 + (-8) 的正确答案。这说明了,2的补码表示法可以将加法运算规则,扩展到整个整数集,从而用一套电路就可以实现全部整数的加法。
补码的本质:
要将正数转成对应的负数,其实只要用0减去这个数就可以了。比如,-8其实就是0-8。
已知8的二进制是00001000,-8就可以用下面的式子求出:
 00000000
-00001000
---------
因为00000000(被减数)小于0000100(减数),所以不够减。请回忆一下小学算术,如果被减数的某一位小于减数,我们怎么办?很简单,问上一位借1就可以了。
所以,0000000也问上一位借了1,也就是说,被减数其实是100000000,算式也就改写成:
100000000
-00001000
---------
 11111000
进一步观察,可以发现100000000 = 11111111 + 1,所以上面的式子可以拆成两个:
 11111111
-00001000
---------
 11110111
+00000001
---------
 11111000
补码的两个转换步骤就是这么来的。(其中的 1111 1000 就是-8的补码,是由对 000 1000 取反得到111 0111 加1 最终得到 111 1000,最后加上符号位1就是1111 1000)。这就是补码计算规则的由来。

###十进制的0.1 为什么不能用二进制很好的表示
0.1×2=0.2 …0

0.2×2=0.4 …0

0.4×2=0.8 …0

0.8×2=1.6…1

0.6×2=1.2…1

0.2×2=0.4…0

是无限循环的。所以。。。。你懂的!

###JAVA整型计算存在的问题
####溢出

int max=Integer.MAX_VALUE;
	System.out.println(max);//2147483647
	System.out.println(Integer.toBinaryString(max));
	System.out.println(Integer.toBinaryString(1));
	System.out.println(max+1);//-2147483648

计算过程
01111111111111111111111111111111
+
00000000000000000000000000000001=
11111111111111111111111111111110(-2147483648)

JAVAfloat、double计算时的问题

精度问题1
类型符号位尾数
float(32位)1位,符号位8位,幂尾数,23位
double(64位)1位,符号位8位,幂尾数,53位

float和double虽然可以表示很大的数,但是他们的精度其实只有23位和53位

精度问题2

一些十进制的小数在二进制中是无法表示的

使用BigDecimal来解决精度问题

来看一下BigDecimal的构造函数

/**
     * Translates a character array representation of a
     * {@code BigDecimal} into a {@code BigDecimal}, accepting the
     * same sequence of characters as the {@link #BigDecimal(String)}
     * constructor, while allowing a sub-array to be specified.
     *
     * <p>Note that if the sequence of characters is already available
     * within a character array, using this constructor is faster than
     * converting the {@code char} array to string and using the
     * {@code BigDecimal(String)} constructor .
     *
     * @param  in {@code char} array that is the source of characters.
     * @param  offset first character in the array to inspect.
     * @param  len number of characters to consider.
     * @throws NumberFormatException if {@code in} is not a valid
     *         representation of a {@code BigDecimal} or the defined subarray
     *         is not wholly within {@code in}.
     * @since  1.5
     */
    public BigDecimal(char[] in, int offset, int len) {
        // protect against huge length.
        if (offset+len > in.length || offset < 0)
            throw new NumberFormatException();
        // This is the primary string to BigDecimal constructor; all
        // incoming strings end up here; it uses explicit (inline)
        // parsing for speed and generates at most one intermediate
        // (temporary) object (a char[] array) for non-compact case.

        // Use locals for all fields values until completion
        int prec = 0;                 // record precision value
        int scl = 0;                  // record scale value
        long rs = 0;                  // the compact value in long
        BigInteger rb = null;         // the inflated value in BigInteger

        // use array bounds checking to handle too-long, len == 0,
        // bad offset, etc.
        try {
            // handle the sign
            boolean isneg = false;          // assume positive
            if (in[offset] == '-') {
                isneg = true;               // leading minus means negative
                offset++;
                len--;
            } else if (in[offset] == '+') { // leading + allowed
                offset++;
                len--;
            }

            // should now be at numeric part of the significand
            boolean dot = false;             // true when there is a '.'
            int cfirst = offset;             // record start of integer
            long exp = 0;                    // exponent
            char c;                          // current character

            boolean isCompact = (len <= MAX_COMPACT_DIGITS);
            // integer significand array & idx is the index to it. The array
            // is ONLY used when we can't use a compact representation.
            char coeff[] = isCompact ? null : new char[len];
            int idx = 0;

            for (; len > 0; offset++, len--) {
                c = in[offset];
                // have digit
                if ((c >= '0' && c <= '9') || Character.isDigit(c)) {
                    // First compact case, we need not to preserve the character
                    // and we can just compute the value in place.
                    if (isCompact) {
                        int digit = Character.digit(c, 10);
                        if (digit == 0) {
                            if (prec == 0)
                                prec = 1;
                            else if (rs != 0) {
                                rs *= 10;
                                ++prec;
                            } // else digit is a redundant leading zero
                        } else {
                            if (prec != 1 || rs != 0)
                                ++prec; // prec unchanged if preceded by 0s
                            rs = rs * 10 + digit;
                        }
                    } else { // the unscaled value is likely a BigInteger object.
                        if (c == '0' || Character.digit(c, 10) == 0) {
                            if (prec == 0) {
                                coeff[idx] = c;
                                prec = 1;
                            } else if (idx != 0) {
                                coeff[idx++] = c;
                                ++prec;
                            } // else c must be a redundant leading zero
                        } else {
                            if (prec != 1 || idx != 0)
                                ++prec; // prec unchanged if preceded by 0s
                            coeff[idx++] = c;
                        }
                    }
                    if (dot)
                        ++scl;
                    continue;
                }
                // have dot
                if (c == '.') {
                    // have dot
                    if (dot)         // two dots
                        throw new NumberFormatException();
                    dot = true;
                    continue;
                }
                // exponent expected
                if ((c != 'e') && (c != 'E'))
                    throw new NumberFormatException();
                offset++;
                c = in[offset];
                len--;
                boolean negexp = (c == '-');
                // optional sign
                if (negexp || c == '+') {
                    offset++;
                    c = in[offset];
                    len--;
                }
                if (len <= 0)    // no exponent digits
                    throw new NumberFormatException();
                // skip leading zeros in the exponent
                while (len > 10 && Character.digit(c, 10) == 0) {
                    offset++;
                    c = in[offset];
                    len--;
                }
                if (len > 10)  // too many nonzero exponent digits
                    throw new NumberFormatException();
                // c now holds first digit of exponent
                for (;; len--) {
                    int v;
                    if (c >= '0' && c <= '9') {
                        v = c - '0';
                    } else {
                        v = Character.digit(c, 10);
                        if (v < 0)            // not a digit
                            throw new NumberFormatException();
                    }
                    exp = exp * 10 + v;
                    if (len == 1)
                        break;               // that was final character
                    offset++;
                    c = in[offset];
                }
                if (negexp)                  // apply sign
                    exp = -exp;
                // Next test is required for backwards compatibility
                if ((int)exp != exp)         // overflow
                    throw new NumberFormatException();
                break;                       // [saves a test]
            }
            // here when no characters left
            if (prec == 0)              // no digits found
                throw new NumberFormatException();

            // Adjust scale if exp is not zero.
            if (exp != 0) {                  // had significant exponent
                // Can't call checkScale which relies on proper fields value
                long adjustedScale = scl - exp;
                if (adjustedScale > Integer.MAX_VALUE ||
                    adjustedScale < Integer.MIN_VALUE)
                    throw new NumberFormatException("Scale out of range.");
                scl = (int)adjustedScale;
            }

            // Remove leading zeros from precision (digits count)
            if (isCompact) {
                rs = isneg ? -rs : rs;
            } else {
                char quick[];
                if (!isneg) {
                    quick = (coeff.length != prec) ?
                        Arrays.copyOf(coeff, prec) : coeff;
                } else {
                    quick = new char[prec + 1];
                    quick[0] = '-';
                    System.arraycopy(coeff, 0, quick, 1, prec);
                }
                rb = new BigInteger(quick);
                rs = compactValFor(rb);
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new NumberFormatException();
        } catch (NegativeArraySizeException e) {
            throw new NumberFormatException();
        }
        this.scale = scl;
        this.precision = prec;
        this.intCompact = rs;
        this.intVal = (rs != INFLATED) ? null : rb;
    }

可以看出来,BigDecimal的基本思想就是用Long或者BigInteger来存储数值的所有数字,同时还存储了精度,与小数点的位置。
如果char[]的length小于MAX_COMPACT_DIGITS,那么久直接用Long型的intCompact来存储,否则使用BigInteger来储值。
再来看下BigDecimal的加法

 /**
     * Returns a {@code BigDecimal} whose value is {@code (this +
     * augend)}, and whose scale is {@code max(this.scale(),
     * augend.scale())}.
     *
     * @param  augend value to be added to this {@code BigDecimal}.
     * @return {@code this + augend}
     */
    public BigDecimal add(BigDecimal augend) {
        long xs = this.intCompact;
        long ys = augend.intCompact;
        BigInteger fst = (xs != INFLATED) ? null : this.intVal;
        BigInteger snd = (ys != INFLATED) ? null : augend.intVal;
        int rscale = this.scale;

        long sdiff = (long)rscale - augend.scale;
        if (sdiff != 0) {
            if (sdiff < 0) {
                int raise = checkScale(-sdiff);
                rscale = augend.scale;
                if (xs == INFLATED ||
                    (xs = longMultiplyPowerTen(xs, raise)) == INFLATED)
                    fst = bigMultiplyPowerTen(raise);
            } else {
                int raise = augend.checkScale(sdiff);
                if (ys == INFLATED ||
                    (ys = longMultiplyPowerTen(ys, raise)) == INFLATED)
                    snd = augend.bigMultiplyPowerTen(raise);
            }
        }
        if (xs != INFLATED && ys != INFLATED) {
            long sum = xs + ys;
            // See "Hacker's Delight" section 2-12 for explanation of
            // the overflow test.
            if ( (((sum ^ xs) & (sum ^ ys))) >= 0L) // not overflowed
                return BigDecimal.valueOf(sum, rscale);
        }
        if (fst == null)
            fst = BigInteger.valueOf(xs);
        if (snd == null)
            snd = BigInteger.valueOf(ys);
        BigInteger sum = fst.add(snd);
        return (fst.signum == snd.signum) ?
            new BigDecimal(sum, INFLATED, rscale, 0) :
            new BigDecimal(sum, rscale);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值