二进制中的原码、反码、补码
对于有符号数而言:
(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)
类型 | 符号位 | 幂 | 尾数 |
---|---|---|---|
float(32位) | 1位,符号位 | 8位,幂 | 尾数,23位 |
double(64位) | 1位,符号位 | 8位,幂 | 尾数,53位 |
float和double虽然可以表示很大的数,但是他们的精度其实只有23位和53位
一些十进制的小数在二进制中是无法表示的
来看一下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.
*
* 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);
}