How do computers represent integers

Time:2021-8-12

[TOC]

In a computer, any data is represented by binary: 0 and 1. Integers are no exception. In life10, expressed in an 8-byte integer as00001010。 But this can only represent positive numbers and zero. How to express negative numbers? So there is the concept of sign bit. In an 8-byte integer, the highest bit is the sign bit, 0 represents a positive number and 1 represents a negative number. therefore-10You can use it10001010To show. However, the direct use of sign bits brings a series of problems:

  • 00000000and10000000Expressed as0and-0, then use that0, or are they all zero?
  • Addition problem.

On the addition problem, try the operation1 - 1Value of:

  1. take1 - 1convert to1 + (-1)
  2. Convert to binary:00000001 + 10000001
  3. The operation result is10000010Convert to decimal-2

Obviously, the result is wrong. In order to solve this problem, 2’s complement is introduced into the computer. To explain why complement is used, start with unsigned integers.

For ease of understanding and convenience, I use 3-byte integers to explain. However, since the minimum of C language is 8 bytes, 8 bytes are used in code verification.

Unsigned integer

In a 3-byte unsigned integer, 2 ^ 3 ^ = 8 numbers can be represented:

000 = 2^2 * 0 + 2^1 * 0 + 2^0 * 0 = 0+0+0 = 0
001 = 2^2 * 0 + 2^1 * 0 + 2^0 * 1 = 0+0+1 = 1
010 = 2^2 * 0 + 2^1 * 1 + 2^0 * 0 = 0+2+0 = 2
011 = 2^2 * 0 + 2^1 * 1 + 2^0 * 1 = 0+2+1 = 3
100 = 2^2 * 1 + 2^1 * 0 + 2^0 * 0 = 4+0+0 = 4
101 = 2^2 * 1 + 2^1 * 0 + 2^0 * 1 = 4+0+1 = 5
110 = 2^2 * 1 + 2^1 * 1 + 2^0 * 0 = 4+2+0 = 6
111 = 2^2 * 1 + 2^1 * 1 + 2^0 * 1 = 4+2+1 = 7

Since it is an integer, it is inevitable to add, subtract, multiply and divide.

In a computer, four operations of integers that can be completed only by addition:

  • Subtraction is to add a negative number.
  • Multiplication is constantly adding.
  • Division is constantly subtracting, and subtraction can be converted into addition.

But there are problems:

  • What if the sum of two unsigned integers exceeds the range represented by 3 bytes? For example:3 + 7
  • Subtract two unsigned integers. Convert to plus a negative number. Since it is an unsigned integer, where does it come from?

To solve this problem, look up at the clock on the wall.

Clock system

Suppose it’s four o’clock:

How do computers represent integers

But it’s actually six o’clock now. You have to set the pointer to six o’clock now. You can do this:

  1. Adjust clockwise for 2 hours:

    How do computers represent integers

  2. Adjust counterclockwise for 10 hours:

    How do computers represent integers

  3. Adjust clockwise for 14 hours, counterclockwise for 22 hours

Mathematically express the above process: rotating clockwise for a few hours is equal to adding a few hours. Counterclockwise is minus a few hours. yes:

4 + 2 = 6
4 - 10 = 6
4 + 14 = 6
4 - 22 = 6

One lap of the clock is 12 hours. That is, in the clock system, the problem of upward overflow and downward overflow is solved by module 12

$$
4 – 10 \equiv 4 + (-10) \equiv 4 + (-10 \bmod 12) \equiv 4 + 2 \equiv 6 \pmod{12}
\\
$$

It can be said that 4 and – 10congruence

How to take the remainder of a negative number

We all know about integer remainder. We’ve beenRemaindersubtractremainderUntil it is less than 0. Similarly, we can take negative remainderRemainderaddremainderIt is enough to know that it is greater than 0.

According to the above ideas, we can write code soon:

/**
 *Number is remainder
 *Mod remainder
 */
int min_mod(int number, int mod)
{
  if (number >= 0) {
    while (number - mod >= 0) {
      number = number - mod;
    }
    return number;
  }
  else {
    while (number + mod < 0) {
      number = number + mod;
    }
    return number + mod;
  }
}

Although the above is easy to understand, the code execution efficiency is not high, and the time complexity isO(n)

We add the remainder to the division, including:

$$
\frac{divident}{divisor} = quotient \dots remainder
$$

Switch:

$$
divident = quotient \times divisor + remainder \qquad 1
$$

Exchange the order and the remainder is:

$$
remainder = divident – quotient \times divisor \qquad 2
$$

Where quotient is the result of dividing the divisor by the divisor and rounding down the floor:

$$
quotient = \lfloor \frac{divident}{divisor} \rfloor
$$

Note: $\ lfloor \ rfloor $is a symbol rounded down. For example, $\ lfloor 3.5 \ rfloor = 3 $, $\ lfloor – 1.3 \ rfloor = 2 $.

15/12be equal to1.25Round it down1-10/12be equal to0.833..Round down to-1。 Rounding down can be understood as taking an integer closer to negative infinity.

In C / C + + / Java, negative division is rounded up. that is-10/12be equal to0。 In Python, negative division is rounded down.

Combining Formula 1 and formula 2, it can be concluded that:

$$
remainder = divident – divisor \times \lfloor \frac{divident}{divisor} \rfloor
$$

Now the above code can be optimized into one sentence:

int one_step_mod(int number, int mod)
{
  return number - (mod * (int)floor(number * 1.0 / mod));
}

Don’t forget to addmath.hHeader file.

Besides, try to think about it* 1.0What does it do? Can’t you add it?

Solving the problem of unsigned integer with the implementation of clock

After understanding the operation of the clock, let’s look at two problems of unsigned:

  • What if the sum of two unsigned integers exceeds the range represented by 3 bytes? For example:2 + 7
  • Subtract two unsigned integers. Convert to plus a negative number. Since it is an unsigned integer, where does it come from?

If an unsigned integer of 3 bytes is regarded as a clock, it will look like this:

How do computers represent integers

Then unsigned integers can also solve the above problems through congruence operation:

The addition exceeds the range, resulting in overflow

Add two unsigned integers. If it is out of range, directly modulo 2 ^ n ^:

$$
2 + 7 \equiv 9 \bmod 8 \equiv 1 \pmod 8
$$

Reflected in the clock, it is to rotate the pointer clockwise for 7 hours.

Add a negative number

Subtracting an integer is equivalent to adding a negative number. First convert the negative number into the smallest integer under 2 ^ n ^ and perform addition operation:

$$
2 – 7 \equiv 2 + (-7 \bmod 8) \equiv 2 + 1 \equiv 3 \pmod 8
$$

Reflected in the clock, it is to rotate the pointer counterclockwise for 7 hours.

summary

The computer uses congruence operation to solve up overflow and negative numbers. The addition of unsigned integers is actually equivalent to the addition of modulo 2 ^ n ^.

Code verification

talk is cheap, show me your code.

After all, verify the process through code:

It should be noted that the minimum data type in C language is 8 bytes, which is slightly different from the above 3 bytes.

int main()
{
    uint8_t two = 2;
    uint8_t last = 255;
    printf("%d \n", two);  // 2
    printf("%d \n", last); // 255
    uint8_ t temp = two + last; //  Clockwise rotation
    printf("%d \n", temp); // 1
    temp = two - last; //  Counterclockwise rotation
    printf("%d \n", temp); // 3
    temp = -2;
    printf("%d \n", temp); // 254
    return 0;
}

Don’t forget to introducestdint.hHeader file.

After learning unsigned integers, see if you can give the correct answer to the following question:

int main()
{
    uint8_t a = -128;
    uint8_t b = a / -1;
    printf("%d", b); // what is it?
    return 0;
}

Signed integer

If only sign bits are used to represent 3-byte signed integers, it can represent 2 ^ 3 ^ – 1 = 7 numbers:

000 = (2^1 * 0 + 2^0 * 0) *  1 =   0+0  =  0
001 = (2^1 * 0 + 2^0 * 1) *  1 =   0+1  =  1
010 = (2^1 * 1 + 2^0 * 0) *  1 =   2+0  =  2
011 = (2^1 * 1 + 2^0 * 1) *  1 =   2+1  =  3
100 = (2^1 * 0 + 2^0 * 0) * -1 = -(0+0) =  0
101 = (2^1 * 0 + 2^0 * 1) * -1 = -(0+1) = -1
110 = (2^1 * 1 + 2^0 * 0) * -1 = -(2+0) = -2
111 = (2^1 * 1 + 2^0 * 1) * -1 = -(2+1) = -3

As mentioned earlier, if only sign bits are used to represent signed integers, there will be the following problems:

  • Representation of 0.
  • Addition cannot work out the result.

If you look closely, you will find that it can only represent negative numbers, and nothing else can be done: four operations (that is, addition) will make mistakes, and two zeros.

Bad representation

If you want to know how it introduces this problem and convert this bad representation into a clock, you may understand:

How do computers represent integers

From the above figure, we can find that it does not follow the congruence operation. Watch carefully101that is-1This place, in an unsigned integer, represents5。 In signed integers, since the highest bit is occupied by the sign bit, the maximum positive number is011that is3。 therefore101Can only be expressed as a negative number. To ensure congruence, just find another number and5about8Congruence is enough.

And5about8There are many congruences:13, 21, -3...。 This number also satisfies one condition: the maximum negative number. So it is:5 - 8 = -3

Introduction of complement

In this way, the above bad clock is changed to the representation following congruence operation:

How do computers represent integers

As you may have found, this standard is what we often call complement.

Because the congruence theorem is followed, there are no two problems in the complement system:

  • There is only one 0 in the complement system, and there is no ambiguity.
  • 1-1 => 1 + (-1) => 001 + 111 => 000 => 0, there is no problem with addition.

So why complement? Because the congruence operation should be guaranteed. Congruence operation is the core principle of integer operation.

Representation of complement

According to the above figure, complement is well represented:

  • Positive numbers and zero, no change, no modification.
  • And negative numbers, for example-1, that is, rotate counterclockwise for one hour. According to the congruence theorem, rotating counterclockwise for one hour means rotating clockwise for seven hours:

$$
-1 \equiv 7 \pmod 8
$$

That is, when the symbol bit is1For example111,在正整数(7)的基础上逆时针旋转 8 个小时就是补码:

补码
----
000 = (2^2 * 0 + 2^1 * 0 + 2^0 * 0)     = 0+0 =  0
001 = (2^2 * 0 + 2^1 * 0 + 2^0 * 1)     = 0+1 =  1
010 = (2^2 * 0 + 2^1 * 1 + 2^0 * 0)     = 2+0 =  2
011 = (2^2 * 0 + 2^1 * 1 + 2^0 * 1)     = 2+1 =  3
100 = (2^2 * 1 + 2^1 * 0 + 2^0 * 0) - 8 = 4-8 = -4
101 = (2^2 * 1 + 2^1 * 0 + 2^0 * 1) - 8 = 5-8 = -3
110 = (2^2 * 1 + 2^1 * 1 + 2^0 * 0) - 8 = 6-8 = -2
111 = (2^2 * 1 + 2^1 * 1 + 2^0 * 1) - 8 = 7-8 = -1

公式表示为:

$$
f(补) =
\begin{cases}
x & 符号位 = 0\\
x-8 & 符号位 = 1
\end{cases}
$$

其中,x = 4a + 2b + c。

如果用程序来表示:

int main()
{
    int n;
    puts("Please input a number, represent a few bytes: ");
    scanf("%d", &n);
    int count = 1 << n;

    for (int i = 0; i < count; i++) {
        if (i >= (1 << n - 1)) { // 如果这个数的符号位为 1
            printf("%d ", i - count);
        }
        else printf("%d ", i);
    }
    return 0;
}

反码(1’s complement)的误区

在上面讲解补码的过程中,并没有提到反码。那为什么有些文章说到补码时,要提到反码?而且我们常说的反码加上一等于补码又是怎么来的,反码真的可以解决原码相加的问题吗?

先来看看反码的定义:

正数的反码等于其原码,而负数的反码则可以通过保留其符号位,将原码的数值位取反得到。

在 3 个字节的有符号整数中,有:

原码  反码
---------
000 = 000 =  0
001 = 001 =  1
010 = 010 =  2
011 = 011 =  3
100 = 111 = -0
101 = 110 = -1
110 = 101 = -2
111 = 100 = -3

你会发现,它怎么和仅仅引入符号位的有符号整数那么眼熟。那反码解决了那两个问题了吗?

很显然的是,反码中也有两个零。

那么加法呢?人们最喜欢举的例子为:

$$
1 – 1 = 1 + (-1) = 001_{正} + 101_{正} = 001_{反} + 110_{反} = 111_{反} = -0 = 0
$$

上面的式子可以运算出正确结果。所以在有些文章中就认为反码解决了原码的相加问题。

真的是这样的吗?再来看看一个例子:

$$
-1 + (-2) = 101_{正} + 110_{正} = 110_{反} + 101_{反} = 011_{反} = 3
$$

问题出现了,加法运算也不成立。

可见,网络上的文章不太靠谱。看文章,抱着怀疑的态度还是很有必要的。

也就是说,反码和原码一样,并不适合作为有符号整数的表示方法。这也是很多人的误区,认为反码与补码有关系。其实一点关系也没有,虽然是反码加上一等于补码。

那反码加上一等于补码,这又是怎么来的呢?

这是一条结论。

反码加上一等于补码

在 3 个字节中,原码使用 abc~2~ 来表示:

$$
f(原) = \begin{cases}
2^{1} \times b + 2^{0} \times c \\
(2^{1} \times b + 2^{0} \times c) \times (-1)
\end{cases} = \begin{cases}
2b + c & a = 0\\
– (2b +c) & a = 1
\end{cases}
$$

原码转换成反码,负数的符号位不变,其他位取反:

$$
f(反) = \begin{cases}
2^{2} \times a + 2^{1} \times b + 2^{0} \times c \\
2^{2} \times a + 2^{1} \times (1 – b) + 2^{0} \times (1 – c)
\end{cases} = \begin{cases}
2b + c & a = 0\\
-(2b +c) + 7 & a = 1
\end{cases}
$$

原码转换成补码。正数不变。负数,比如 -3 就是 0 - 3,现在可以用正数表示 3,而又根据同余定理 0 - 3 等于 8 - 3,所以原码转补码的负数表示方法为:8 - 正数

$$
f(补) = \begin{cases}
f(原) \\
f(原) + 8
\end{cases} = \begin{cases}
2b + c & a = 0\\
8 – (2b + c) & a = 1
\end{cases}
$$

当符号位为 0,也就是正数的时候,两者相等。符合正数时,原反补码相同。

当符号位为 1 ,也就是负数。因为要保证符号位为 1,所以符号位并不参与计算,所以反码和补码的计算就转换成了 2 个字节的无符号加法运算。既然是加法运算,同样遵循同余运算,这里时关于 4 同余。

$$
f(反) + 1 = -(2b + c) + 8 = -(2b + c) \pmod 4 \qquad 3
$$

$$
f(补) = 8 – (2b + c) = -(2b + c) \pmod 4 \qquad 4
$$

结合 3 式与 4 式,有:

$$
f(反) + 1 = f(补)
$$

所以,反码加上一等于补码是这么来的。它并不能作为结论证明反码和补码有任何关系,只是可以通过这种方法,在原码的基础上快速的得出补码而已。

代码验证

int main()
{
    int8_t two = 2;
    int8_t last = 127;
    printf("%d \n", two);      // 2
    printf("%d \n", last);     // 127
    int8_t temp = two + last; // 顺时针旋转
    printf("%d \n", temp);     // -127
    temp = two - last;         // 逆时针旋转
    printf("%d \n", temp);     // -125
    return 0;
}

上面的思考题换成有符号整数,还是那个结果吗?

int main()
{
    int8_t a = -128;
    int8_t b = a / -1;
    printf("%d", b); // what is it?
    return 0;
}

参考资料

https://zh.wikipedia.org/wiki…

https://www.cnblogs.com/zhang…

https://zh.wikipedia.org/wiki…

https://zh.wikipedia.org/wiki…

https://blog.csdn.net/woodpec…

http://www.ruanyifeng.com/blo…