[Integer data and floating point data] How are they stored in memory? Understanding of original code, inverse code and complement code

Article directory

  • Preface
  • 1. Introduction to data types
  • 2. Storage of integers in memory
    • 2.1 Original code, complement code, complement code
    • 2.2 Little-endian storage and big-endian storage
    • 2.3 Signed data and unsigned data
    • 2.4 Exercise: Determine whether the storage mode of the current machine is big endian or little endian?
  • 3. Reading of integer data
  • 4. Storage of floating-point data in memory
    • 4.1 Little squirrel: give a chestnut
    • 4.2 Storage rules for floating point numbers IEEE754
  • 5. Reading of floating point data
    • 5.1 Reading of exponent bit E
    • 5.2 Little squirrel: eat a chestnut
  • Summarize

Foreword

There is a lot of data in the computer, so how are they stored in the memory? All data in the computer is stored by a series of binary sequences such as 010101… This article takes integer data and floating-point data as examples to discuss how these two types of data are stored in memory?

1. Introduction to data types

char //Character data type
short //short integer type short[int]---"[]" means that the content in [] can be omitted
int //integer type
long //Long integer type long [int]
long long //long long integer type long long[int]
float //Single precision floating point type
double //double precision floating point type

The meaning of the type: The size of the memory space opened using this type occupies several bytes.

  • We classify char, short, int, long, long long types as integer data.
  • Classify float and double types as floating-point data.

2. Storage of integers in memory

int a = 10;
int b = -10;

So how are a and b stored in memory?
Let’s first understand some concepts:

2.1 Original code, inverse code, complement code

There are three binary representation methods for integers in computers, including original code, complement code and complement code.
The three representation methods all have two parts: sign bit and numeric bit. In the binary sequence, the highest bit is the sign bit. code>, use 0 to represent “positive number” and 1 to represent “negative number”.

Among them, the original code, inverse code and complement code of the positive number of are the same. The three ways to represent negative numbers are different:

  1. Original code: directly translate the numerical value into a binary sequence in the form of positive and negative numbers to obtain the original code.
  2. Negative code: Leave the sign bit (highest bit) of the original code unchanged. The numerical bits are inverted bit by bit to obtain the complement code.
  3. Complement code: Add the complement code + 1 to get the complement code.

In memory, integer data is stored in memory in two’s complement form.

#include <stdio.h>

int main()
{<!-- -->
int a = 10;
//a is a positive number, the original code, inverse code, and complement code are all the same
//The original code of a: 00000000 00000000 00000000 00001010
//The complement of a: 00000000 00000000 00000000 00001010
//The complement of a: 00000000 00000000 00000000 00001010
//Hexadecimal representation: 0x00 00 00 0a
int b = -10;
//b is a negative number, the original code, inverse code, and complement code are different
//The original code of b: 10000000 00000000 00000000 00001010
//The complement of b: 11111111 11111111 11111111 11110101
//The complement of b: 11111111 11111111 11111111 11110110
//Hexadecimal representation: 0xff ff ff f6;

return 0;
}

[Note] The debugging skills in VS will not be described in detail here. If there is anything you don’t understand below, please check the detailed debugging skills [Visual Stdio2022 debugging skills]
Visual Stdio2022 Debugging Tips

We press F10 to debug and select Debug-Window-Memory-Memory Window 1 to view the data in the memory.

We press F10 to debug and run (create a and b variables in memory). In the memory window, address bar: enter & a to view the data of variable a in memory.

We can see that the a variable in the memory stores 0a 00 00 00, but we calculated that a should store 0x00 00 00 0a in the memory. Why is the storage order different?

  • Because in computers, the compiler stores data in memory in little-endian format.
  • Why does little-endian storage exist? What is big-endian storage?

2.2 Little-endian storage and big-endian storage

Reason: In computer systems, we measure bytes. Each address corresponds to a byte, and a byte is 8 bits. But in C language, in addition to the 8-bit char type, there are also 16-bit short type, 32-bit int type, etc. For processors with more than 8 bits, such as 16-bit/32-bit processors, since the width of the register is greater than one byte, there must be a problem of how to arrange multiple bytes. This leads to big-endian storage and little-endian storage modes.

  • [Big endian storage mode]: means that the high bits of data are stored in the low addresses of the memory, and the low bits of the data are stored in the high addresses of the memory.

  • [Little endian storage mode]: means that the high bits of data are stored in the high addresses of the memory, and the low bits of the data are stored in the low addresses of the memory.

  • For example: 16bit short type x, the address in the memory is: 0x1234, then 0x12 is the high byte and 0x34 is the low byte.

  • For big-endian storage mode, store 0x12 in the low address and 0x34 in the high address. The representation in memory is: 12 34.

  • For little-endian storage mode, store 0x12 in the high address and 0x34 in the low address. The representation in memory is: 34 12.

So the variable a = 10; is stored in the memory in little-endian storage mode. The two’s complement of a is: 0x 00 00 00 0a, which is represented in the memory as: 0a 00 00 00.
b = 10; The two’s complement of b: 0x ff ff ff f5, represented in memory as: f5 ff ff ff.

2.3 Signed data and unsigned data

In C language, the data marked with unsigned is unsigned data, and the data marked with signed is signed data.
The signed in front of signed data can be omitted, and all defined variables are signed by default.

  • What is the difference between signed and unsigned?
  • The data types usually defined are all signed by default.
  • signed char and char, signed int and int…the data defined is a type.
  • Unsigned data, the highest bit of the binary sequence is not the sign bit, and each bit of the binary sequence is a data bit.
  • Since the highest bit of unsigned data is also a data bit, the data range represented is larger.
  • Unsigned data can only represent positive numbers.

For example:

int a = -10;
Signed variable a is represented in memory as: 11111111 11111111 11111111 11110110
Read as signed data: Number -10.

unsigned int b = -10;
Unsigned variable b, represented in memory as: 11111111 11111111 11111111 11110110
Read as unsigned data: the number 4294967286.

This is because the highest bit is not a sign bit but a valid value bit.

#include <stdio.h>

void printBinary(unsigned int num) {<!-- -->
    int i;
    for (i = 31; i >= 0; i--) {<!-- -->
        if ((num >> i) & amp; 1) {<!-- -->
            printf("1");
        }
        else {<!-- -->
            printf("0");
        }
    }
}

int main()
{<!-- -->
int a = -10;
unsigned int b = -10;

printf("a = %d\
", a);
printf("b = %u\
", b);
    printBinary(b);

return 0;
}

2.4 Exercise: Determine whether the storage mode of the current machine is big endian or little endian?

Baidu 2015 system engineer written examination questions:
Please briefly describe the concepts of big-endian and little-endian, and design a small program to determine the byte order of the current machine. (10 points)

#include <stdio.h>

int check_sys()
{<!-- -->
int i = 1;
//00000000 00000000 00000000 00000001
//0x00 00 00 01
//Take out the address of i and force it into char* type.
//Dereference to get the first byte sequence
//If it is big-endian storage, store it in the memory: 00 00 00 01 -- take the first byte and the value is: 0
//If it is little-endian storage, store it in the memory: 01 00 00 00 -- take the first byte and the value is: 1
return (*(char*) & amp;i);
}

int main()
{<!-- -->
int ret = check_sys();
if (ret)
{<!-- -->
printf("little endian");
}
else
{<!-- -->
printf("big endian");
}

return 0;
}

3. Reading of integer data

For reading integer data, we show it in the form of exercises
(1) Example 1:

//What is output?
#include <stdio.h>
int main()
{<!-- -->
char a = -1;
signed char b = -1;
unsigned char c = -1;
\t
printf("a=%d,b=%d,c=%d", a, b, c);
\t
return 0;
}

Code explanation:

#include <stdio.h>

int main()
{
char a = -1;
//Original code: 10000000 00000000 00000000 00000001
//Inverse code: 11111111 11111111 11111111 11111110
//Complement code: 11111111 11111111 11111111 11111111
//a is of type char, so it can only store 1 byte and 8 bits in the memory, which is the lower 8 bits of the data.
//a storage in memory: 11111111


signed char b = -1;
//signed char and char are equivalent
//b storage in memory: 11111111


unsigned char c = -1;
//Every bit of unsigned char is a valid bit (numeric bit)
//c storage in memory: 1111 1111


printf("a=%d,b=%d,c=%d", a, b, c);
//Data a is printed in the form of %d type, and integer promotion occurs
//a is promoted from char type integer type to int type
//a is a signed char type, the highest bit complements the sign bit
//The storage of a in memory: 11111111, the sign bit is: 1
//11111111 11111111 11111111 11111111 -- complement
//Data a is printed as %d type, and the compiler prints (11111111 11111111 11111111 11111111 -- complement) as signed integer data
//10000000 00000000 00000000 00000001 -- negate and then + 1 to get the original code
//The value of a is: -1

//Data b is printed in %d type, integer promotion, same as a,
//The value of b is: -1

//Data c is printed as %d type, integer promotion
//Convert from char type to int type,
//a is unsigned char, so the highest bit is filled with 0
//00000000 00000000 00000000 11111111 -- complement
Data a is printed as %d type, and the compiler prints (00000000 00000000 00000000 11111111 -- complement) as signed integer data.
//The highest value is 0, a positive number, the complement of the original code is the same
//The value of c is: 2^8 -1 = 255

return 0;
}

result:

(2) Example 2:

#include <stdio.h>
int main()
{<!-- -->
char a = -128;
printf("%u\
", a);

return 0;
}

Code explanation:

#include <stdio.h>
int main()
{
char a = -128;
//Original code: 10000000 00000000 00000000 10000000
//Inverse code: 11111111 11111111 11111111 01111111
//Complement code: 11111111 11111111 11111111 10000000
//a storage in memory: 10000000


printf("%u\
", a);
//a is printed as %u, unsigned integer type, and integer promotion occurs
//The type of data a is char type, signed char, the highest bit complements the sign bit: 1
//11111111 11111111 11111111 10000000 -- complement
//Print with %u unsigned type, the compiler will print (11111111 11111111 11111111 10000000 -- complement) as an unsigned number
//The result is: (2^32 -1) - (2^8 - 1) = 4294967168

return 0;
}

result:

(3) Example 3:

#include <stdio.h>

int main()
{<!-- -->
char a = 128;
printf("%u\
", a);

return 0;
}

Code explanation:

#include <stdio.h>

int main()
{
char a = 128;
//00000000 00000000 00000000 10000000 -- original code, inverse code, complement code
//a storage in memory: 10000000


printf("%u\
", a);
//a is printed as %u unsigned integer, integer promotion occurs
//a is char type, signed char, the highest bit complements the sign bit,
//a's storage in memory: 10000000, the sign bit is 1
//11111111 11111111 11111111 10000000 -- complement
//Print as unsigned type, the compiler treats (11111111 11111111 11111111 10000000 -- complement) as an unsigned number
//The result is: (2^32 -1) - (2^8 - 1) = 4294967168

return 0;
}

turn out:

(4) Example 4:

#include <stdio.h>

int main()
{<!-- -->
int i = -20;
unsigned int j = 10;

printf("%d\
", i + j);
\t
return 0;
}

Code explanation:

#include <stdio.h>

int main()
{
int i = -20;
//Original code: 10000000 00000000 00000000 00010100
//Inverse code: 11111111 11111111 11111111 11101011
//Complement code: 11111111 11111111 11111111 11101100

unsigned int j = 10;
//Original code, inverse code, complement code: 00000000 00000000 00000000 00001010


printf("%d\
", i + j);
//Perform operations in the form of two's complement, and finally format it into a signed integer
//The data types of i and j are inconsistent, and arithmetic conversion occurs on i
//Complement code: 11111111 11111111 11111111 11101100 -- unsigned i
//
//i: 11111111 11111111 11111111 11101100 -- complement
//j: 00000000 00000000 00000000 00001010 -- complement
  //i + j: 11111111 11111111 11111111 11110110 -- complement
//The result is printed in type %d, and the compiler treats (i + j: 11111111 11111111 11111111 11110110 -- complement) as signed integer data
//i + j is a negative number:
//The complement bitwise negation: 10000000 00000000 00000000 00001001
//The complement bitwise negation + 1: 10000000 00000000 00000000 00001010
//The value of i + j: -10
\t
return 0;
}

The result is:

(5) Example 5:

#include <stdio.h>

int main()
{<!-- -->
unsigned int i;

for (i = 9; i >= 0; i--)
{<!-- -->
printf("%u\
", i);
}

return 0;
}

Code explanation:

#include <stdio.h>

int main()
{
unsigned int i;
//unsigned int, unsigned integer, maximum representation: 32 all ones
//11111111 11111111 11111111 11111111
//That is 2^32 -1
//Range: [0, (2^32-1)]
//
//i>=0 is always true, endless loop
for (i = 9; i >= 0; i--)
{
printf("%u\
", i);
}

return 0;
}

Same reason:

#include <stdio.h>
unsigned char i = 0;
int main()
{<!-- -->
//i is an unsigned character type, the maximum representation (8 all ones): 11111111 -- value: 255
//The value range of i: [0, 255]
//i<=255, always true, endless loop
    for(i = 0;i<=255;i + + )
   {<!-- -->
        printf("hello world\
");
   }
    return 0;
}

(6) Example 6

#include <stdio.h>

int main()
{<!-- -->
char a[1000];
int i;
for (i = 0; i < 1000; i + + )
{<!-- -->
a[i] = -1 - i;
}
printf("%d", strlen(a));

return 0;
}

Code explanation:

#include <stdio.h>

int main()
{
char a[1000];
int i;
\t
for (i = 0; i < 1000; i + + )
{
a[i] = -1 - i;
//Store the value of -1-i in an array of char type
//Each number in the array is of type char.
//char type represents range:
//00000000 -- 0
//00000001 -- 1
//00000010 -- 2
//00000011 -- 3
//...
//01111111 -- 127
//10000000 -- (C language stipulates that this number is)-128
//10000001 - Negated complement -> 11111110 - Negated + 1 -> 11111111 (original code) -127
//10000010 - Negated complement -> 11111101 - Negated + 1 -> 11111110 (original code) -126
//10000011 - Negated complement -> 11111100 - Negated + 1 -> 11111101 (original code) -125
//...
//11111110 - Negated complement -> 10000001 - Negated + 1 -> 10000010 (original code) -2
//11111111 - Negated complement -> 10000000 - Negated + 1 -> 10000001 (original code) -1

//The value range of char type data is: [-128, 127]
//a[i] = -1 - i;
//The values of a[i] are: -1, -2, -3... ...-127, -128, 127, 126... ... 3, 2, 1, 0, -1 , -2, ... ...
//Keep looping
}

printf("%d", strlen(a));
//strlen() finds the length of a string, starting from the starting position until it encounters '\0', and finds the number of characters in between. (The ASCII code value of '\0' is: 0)
//In other words, in array a, starting from a[0] and ending when it reaches the number 0, how many elements are there in between.
//The values of a[i] are: - 1, -2, -3... ... - 127, -128, 127, 126... ... 3, 2, 1, 0,
//Total total: 127*2 + 1 = 255
return 0;
}

result:

4. Storage of floating point data in memory

4.1 Little Squirrel: Give a Chestnut

What is the result of running the following code?

#include <stdio.h>

int main()
{<!-- -->
int n = 9;
float* pFloat = (float*) &n;
printf("The value of n is: %d\
", n);//9
printf("The value of *pFloat is: %f\
", *pFloat);//9.0
*pFloat = 9.0;
printf("The value of n is: %d\
", n);//9
printf("The value of *pFloat is: %f\
", *pFloat);//9.0

return 0;
}

Little Squirrel: See the above comments for the running results O.o!
Running result:

Little Squirrel: Silently withdrew a chestnut QvQ.

Why does this happen?
Because: the way floating-point numbers are stored in memory is different from the way positive numbers are stored in memory, and the results obtained are also different.

4.2 Storage rules for floating point numbers IEEE754

In computers, floating-point numbers are stored according to the international standard IEEE (Institute of Electrical and Electronics Engineering) 754 standard. The standard stipulates that any binary floating-point number V can be expressed in the following form:

  • (-1)^S * (1.M) * 2^e, E=e + 127
  • S represents the sign bit, occupying 1 bit. When S=0, V is a positive number. When S=1, V is a negative number.
  • M represents a significant number, occupies 23 digits, and the value range is: 1 <= M <2.
  • e represents the exponent. E is the exponent code, which is an unsigned number.

IEEE754 standard stipulates:
For a 32-bit floating point number, the highest 1 bit is the sign bit S, the next 8 bits are the exponent E, and the last 23 bits are the valid bit M.

[32-bit floating point number]

For a 64-bit floating point number, the highest 1 bit is the sign bit S, the next 11 bits are the exponent E, and the last 52 bits are the valid bit M.

[64-bit floating point number]


For example:

  • 5.5 in decimal, binary representation: 101.1, equivalent to: 1.011*2^2
  • Then, according to the above standards, it can be written as, (-1)^0 * 1.011 * 2^2
  • So, S=0, 1.M=1.011, E=2 + 127=129

Then the binary storage of decimal 5.5 in memory is:

  • 0 10000001 01100000000000000000000
  • That is 01000000 10110000 00000000 00000000
  • Among them, S=0, E=10000001=129, M=01100000000000000000000

IEEE754 also has some special provisions for the significant digit M and the exponent E:

  1. Why only M is saved as a valid number but not 1.M?

The IEEE754 standard stipulates that when 1.M is stored in the computer, the first digit of this number is always 1 by default, so it can be discarded and only the subsequent M part is saved. For example, when saving 101.100, only the last 100 is saved. When reading, the first 1 and decimal point are added. The purpose of this is to save 1 significant digit, and the accuracy of storage will be improved. For example, in a 32-bit system, only 23 bits are left for the floating point number M. After the first digit is discarded, 24 significant digits can be saved.

  1. Representation of the exponent e

The situation for the index e is more complicated. First of all, E is an unsigned int (usigned int), which means that if E is 8 bits, its value range is: 0 ~ 255; if E is 11 bits, it The value range is: 0 ~ 2047. However, e can be negative in scientific notation, so IEEE754 stipulates that when stored in memory, the exponent e must be added to a fixed number, that is, stored in E = e + 127; for an 8-bit E, this fixed number is 127. For an 11-digit E, this fixed number is 1023.
For example: e of 2^2 is 2, and what is stored in the memory is E = e + 127 = 129, which is 10000001

Small exercise: give an example
Convert decimal: 20.59375 into the IEEE standard 32-bit floating point number binary storage format.

  • First convert the decimal number into a binary number
  • After conversion: 10100.10011
  • Normalized representation: (-1)^0 * 1.010010011* 2^4
  • S=0; M=010010011; e=4, E=e + 127=131
  • The binary number of 131: 10000011
  • So the binary storage format of the final 32-bit floating point number is:
  • 0 10000011 01001001100000000000000
    -i.e. 01000001 10100100 11000000 00000000
    -i.e. 0x41 A4 C0 00

5. Reading of floating point data

Now that we understand how floating point numbers are stored in memory, let’s see how to read floating point numbers from memory?

Reading floating-point data is to sequentially take out S, M, E from the memory and “assemble” it into V=(-a)^S * (1.M) * 2^e, e=E-127; we get V is the retrieved data.

  • For example: In the previous example of this article, decimal 5.5, the binary storage in memory is:
    01000000 10110000 00000000 00000000
  • S occupies 1 position, S=0
  • E occupies 8 bits, E=10000001=129, e=E-127 = 129-127 = 2
  • M occupies 23 digits, 01100000000000000000000
  • V=(-1)^0* 1.011* 2 ^ 2 = 101.1, converted to decimal: 5.5

Note: The value of E needs to be calculated, and the value of M is read directly in binary sequence. Add 1 and a decimal point in front of M, 1.M.

5.1 Reading of exponent bit E

The exponent bit E is taken out from the memory and can be divided into three situations:

  1. E is not all 0 or not all 1

The exponent e of a floating point number is: Subtract 127 from the calculated value of the exponent bit E (subtract the fixed value 1023 from a 64-bit floating point number) to get the real value e, and then add the first 1 and decimal point in front of the significant digit M.

  • For example: the binary form of 0.5 is: 0.1. Since IEEE754 stipulates that the positive part must be 1, so the decimal point needs to be moved 1 place, then it is: 1.0 * 2^(-1), the exponent code E=e + 127 = -1 + 127 = 126, E is expressed as: 01111110, and the mantissa 1.0 takes out the integer part, leaving the decimal part 0, and the padding is 23 digits, 00000000000000000000000, then the binary storage format is: 0 01111110 000000000000000000000000
  1. E is all 0

This is the exponent of a floating point number e = E-127=0-127=-127 (a 64-bit floating point number minus the fixed value 1023). The effective digit M is no longer added to the first 1, but restored to a decimal of 0.xxxxxx. This is done to represent + 0 and -0, as well as very small numbers close to 0.

  1. E is all 1

At this time, e=E-127 = 255-127=128. V=(-1)^S * (1.M) * 2^128, if the significant digits M are all 0, it means plus or minus infinity (the plus or minus depends on the sign bit S).

5.2 Little squirrel: eat a chestnut

So now, let’s analyze the following example:

#include <stdio.h>

int main()
{<!-- -->
int n = 9;
float* pFloat = (float*) &n;
printf("The value of n is: %d\
", n);//9
printf("The value of *pFloat is: %f\
", *pFloat);//9.0
*pFloat = 9.0;
printf("The value of n is: %d\
", n);//9
printf("The value of *pFloat is: %f\
", *pFloat);//9.0

return 0;
}

Code analysis:

#include <stdio.h>

int main()
{
int n = 9;
//n storage in memory:
//00000000 00000000 00000000 00001001 -- original code, inverse code, complement code

float* pFloat = (float*) &n;
//Pointer pFloat points to the address of variable n

printf("The value of n is: %d\
", n);//9
//Print in the form of %d, the compiler will regard (00000000 00000000 00000000 00001001 -- original code, one's complement, one's complement) as a signed integer
//Print result is: 9

printf("The value of *pFloat is: %f\
", *pFloat);//0.000000
//*pFloat dereferences and gets the content pointed to by the pFloat pointer. (00000000 00000000 00000000 00001001 -- original code, inverse code, complement code)
//Print in the form of %f, the compiler will consider (00000000 00000000 00000000 00001001 -- original code, inverse code, complement code) as a single-precision floating point number
//Parse this binary sequence in the form of floating point numbers.
//S=0
//E=00000000 = 0
//M=00000000000000000001001
//At this time we find that the exponent code E (exponent bit) is 0, which means that the exponent of this floating point number is e= E-127 = -127, which will be a very small, very small, wirelessly approaching plus or minus 0 value
//IEEE754 standard stipulates that when E is all 0, the true value of this floating point number is 0
//So output: 0.000000

*pFloat = 9.0;
//Assign the value pointed to by the pointer pFloat to: 9.0
//Storing 9.0 as a single-precision floating point number
//The binary form of 9.0 is: 1001.0 = 1.001*2^3
//Normalized floating point number: (-1)^0 * 1.001 * 2^3, exponent: e=3
//S=0
//E=e + 127=3 + 127=130, binary representation is: 10000010
//M=001 Complete 23 digits, 001000000000000000000000
//The binary storage format of 9.0 is: 0 10000010 00100000000000000000000
//That is: 01000001 00010000 00000000 00000000
\t

printf("The value of n is: %d\
", n);//1,091,567,616
//*pFloat changes the value of n to 9.0 in the form of a floating point number
//n is represented in memory as: 01000001 00010000 00000000 00000000
//Print in the form of %d, the compiler considers (01000001 00010000 00000000 00000000) as signed integer data
//The hexadecimal representation is: 0x41 10 00 00 -- the value is: 1,091,567,616
//Little endian storage is: 00 00 10 41
//So print: 1,091,567,616
\t
printf("The value of *pFloat is: %f\
", *pFloat);//9.0
//Save as floating point data, and then retrieve it as floating point data, so the value taken out remains unchanged, still 9.0
//The following is the detailed process:
//*pFloat dereferences and gets the data in the memory pointed to by pFloat: 01000001 00010000 00000000 00000000
//Printed in the form of %f, the compiler considers (01000001 00010000 00000000 00000000) as a single-precision floating point number
//S=0
//E=100000010=, e=E-127 = 130-127 = 3
//M=001
//Normalized floating point number: (-1)^0 * 1.001 * 2^3 = 1001.0
//Convert to decimal: 9.0
//Print: 9.0

return 0;
}

Debug and run, check the memory data, and find that it is consistent with the data we calculated.

result:

Summary

This article mainly introduces

  • How is integer data stored in memory? Master the original code, inverse code, complement code of integer data in memory, and what is the big-endian storage mode? What is little endian storage mode?
  • How floating-point data is stored in memory, master IEEE754 standardized floating-point numbers, and know that 32-bit floating-point numbers, S occupies 1 bit, E occupies 8 bits, and M occupies 23 bits. 64-bit floating point number, S occupies 1 bit, E occupies 11 bits, and M occupies 52 bits.