Trong hầu hết các ngôn ngữ lập trình, các số dấu phẩy động được thể hiện rất giống ký hiệu khoa học : với số mũ và mantissa (còn được gọi là ý nghĩa). Một con số rất đơn giản 9.2
, thực sự là phần này:
5179139571476070 * 2 -49
Trường hợp số mũ là -49
và mantissa là 5179139571476070
. Lý do không thể biểu diễn một số số thập phân theo cách này là vì cả số mũ và số mũ phải là số nguyên. Nói cách khác, tất cả các số float phải là một số nguyên nhân với công suất nguyên là 2 .
9.2
có thể đơn giản 92/10
, nhưng 10 không thể được biểu thị bằng 2 n nếu n bị giới hạn ở các giá trị nguyên.
Xem dữ liệu
Đầu tiên, một vài chức năng để xem các thành phần tạo ra 32 và 64 bit float
. Đưa ra những điều này nếu bạn chỉ quan tâm đến đầu ra (ví dụ trong Python):
def float_to_bin_parts(number, bits=64):
if bits == 32: # single precision
int_pack = 'I'
float_pack = 'f'
exponent_bits = 8
mantissa_bits = 23
exponent_bias = 127
elif bits == 64: # double precision. all python floats are this
int_pack = 'Q'
float_pack = 'd'
exponent_bits = 11
mantissa_bits = 52
exponent_bias = 1023
else:
raise ValueError, 'bits argument must be 32 or 64'
bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]
Có rất nhiều điều phức tạp đằng sau chức năng đó và nó sẽ khá là khó để giải thích, nhưng nếu bạn quan tâm, tài nguyên quan trọng cho các mục đích của chúng tôi là mô-đun struct .
Python float
là một số chính xác 64 bit. Trong các ngôn ngữ khác như C, C ++, Java và C #, độ chính xác kép có một loại riêng double
, thường được triển khai là 64 bit.
Khi chúng ta gọi hàm đó bằng ví dụ của mình 9.2
, đây là những gì chúng ta nhận được:
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
Giải thích dữ liệu
Bạn sẽ thấy tôi chia giá trị trả về thành ba thành phần. Các thành phần này là:
- Ký tên
- Số mũ
- Mantissa (còn được gọi là Ý nghĩa hoặc Phân số)
Ký tên
Dấu hiệu được lưu trữ trong thành phần đầu tiên dưới dạng một bit. Thật dễ để giải thích: 0
có nghĩa là số float là một số dương; 1
có nghĩa là nó tiêu cực. Bởi vì 9.2
là tích cực, giá trị dấu hiệu của chúng tôi là 0
.
Số mũ
Số mũ được lưu trữ trong thành phần giữa là 11 bit. Trong trường hợp của chúng tôi , 0b10000000010
. Trong thập phân, điều đó đại diện cho giá trị 1026
. Một điều khó hiểu của thành phần này là bạn phải trừ đi một số bằng 2 (# bit) - 1 - 1 để có được số mũ thực sự; trong trường hợp của chúng tôi, điều đó có nghĩa là trừ 0b1111111111
(số thập phân 1023
) để có được số mũ thực sự, 0b00000000011
(số thập phân 3).
Thần chú
Lớp phủ được lưu trữ trong thành phần thứ ba là 52 bit. Tuy nhiên, cũng có một sự giải thích cho thành phần này. Để hiểu vấn đề này, hãy xem xét một số trong ký hiệu khoa học, như thế này:
6.0221413x10 23
Bọ ngựa sẽ là 6.0221413
. Hãy nhớ lại rằng mantissa trong ký hiệu khoa học luôn bắt đầu bằng một chữ số khác không. Điều tương tự cũng đúng với nhị phân, ngoại trừ nhị phân chỉ có hai chữ số: 0
và 1
. Vì vậy, mantissa nhị phân luôn luôn bắt đầu với 1
! Khi một float được lưu trữ, 1
ở phía trước của mantissa nhị phân được bỏ qua để tiết kiệm không gian; chúng ta phải đặt nó trở lại ở phía trước của yếu tố thứ ba của chúng ta để có được câu thần chú thực sự :
1,0010011001100110011001100110011001100110011001100110
Điều này liên quan đến nhiều hơn chỉ là một bổ sung đơn giản, bởi vì các bit được lưu trữ trong thành phần thứ ba của chúng ta thực sự đại diện cho phần phân đoạn của lớp phủ, ở bên phải của điểm cơ số .
Khi xử lý các số thập phân, chúng ta "di chuyển dấu thập phân" bằng cách nhân hoặc chia cho lũy thừa 10. Trong nhị phân, chúng ta có thể làm điều tương tự bằng cách nhân hoặc chia cho lũy thừa của 2. Vì phần tử thứ ba của chúng ta có 52 bit, chúng ta chia nó bằng 2 52 để di chuyển 52 vị trí sang phải:
0,0010011001100110011001100110011001100110011001100110
Trong ký hiệu thập phân, đó là giống như chia 675539944105574
bởi 4503599627370496
để có được 0.1499999999999999
. (Đây là một ví dụ về tỷ lệ có thể được biểu thị chính xác dưới dạng nhị phân, nhưng chỉ xấp xỉ bằng số thập phân; để biết thêm chi tiết, xem: 675539944105574/4503599627370496 .)
Bây giờ chúng tôi đã chuyển đổi thành phần thứ ba thành một số phân số, việc thêm vào 1
sẽ tạo ra lớp phủ thực sự.
Tóm tắt lại các thành phần
- Dấu hiệu (thành phần đầu tiên):
0
cho tích cực, 1
cho tiêu cực
- Số mũ (thành phần giữa): Trừ 2 (# bit) - 1 - 1 để lấy số mũ thực
- Mantissa (thành phần cuối cùng): Chia cho 2 (# bit) và thêm
1
vào để có được mantissa thực sự
Tính số
Đặt cả ba phần lại với nhau, chúng tôi đã cho số nhị phân này:
1,0010011001100110011001100110011001100110011001100110 x 10 11
Mà sau đó chúng ta có thể chuyển đổi từ nhị phân sang thập phân:
1.1499999999999999 x 2 3 (không chính xác!)
Và nhân lên để hiển thị đại diện cuối cùng của số chúng tôi bắt đầu bằng ( 9.2
) sau khi được lưu trữ dưới dạng giá trị dấu phẩy động:
9.1999999999999993
Đại diện như một phân số
9,2
Bây giờ chúng tôi đã tạo số, có thể xây dựng lại thành một phần đơn giản:
1,0010011001100110011001100110011001100110011001100110 x 10 11
Chuyển mantissa sang một số nguyên:
10010011001100110011001100110011001100110011001100110 x 10 11-110100
Chuyển đổi sang số thập phân:
5179139571476070 x 2 3-52
Trừ đi số mũ:
5179139571476070 x 2 -49
Biến số mũ âm thành chia:
5179139571476070/2 49
Nhân số mũ:
5179139571476070/56949953421312
Mà bằng:
9.1999999999999993
9,5
>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']
Bạn đã có thể thấy mantissa chỉ có 4 chữ số theo sau là rất nhiều số không. Nhưng chúng ta hãy đi qua các bước.
Lắp ráp ký hiệu khoa học nhị phân:
1,0011 x 10 11
Thay đổi dấu thập phân:
10011 x 10 11-100
Trừ đi số mũ:
10011 x 10 -1
Nhị phân đến thập phân:
19 x 2 -1
Số mũ âm để chia:
19/2 1
Nhân số mũ:
19/2
Bằng:
9,5
đọc thêm