Có thể (ít nhất là cho IEEE 754 float
và double
giá trị) để tính toán giá trị dấu chấm động vĩ đại nhất qua (pseudo-code):
~(-1.0) | 0.5
Trước khi chúng tôi có thể làm bit twiddling của chúng tôi, chúng tôi sẽ phải chuyển đổi các giá trị dấu phẩy động thành số nguyên và sau đó quay lại. Việc này có thể được thực hiện theo cách sau:
uint64_t m_one, half;
double max;
*(double *)(void *)&m_one = -1.0;
*(double *)(void *)&half = 0.5;
*(uint64_t *)(void *)&max = ~m_one | half;
Vậy nó hoạt động như thế nào? Để làm được điều đó, chúng ta phải biết các giá trị dấu phẩy động sẽ được mã hóa như thế nào.
Bit cao nhất mã hóa dấu, các bit k
tiếp theo mã hóa số mũ và các bit thấp nhất sẽ giữ phần phân số. Đối với quyền hạn của 2
, phần phân số là 0
.
Số mũ sẽ được lưu trữ với độ lệch (offset) là 2**(k-1) - 1
, có nghĩa là số mũ của 0
tương ứng với mẫu có tất cả trừ bit cao nhất.
Có hai mẫu mũ bit với ý nghĩa đặc biệt:
- nếu không bit được thiết lập, giá trị sẽ được
0
nếu phần thập phân là số không; cách khác, giá trị là một subnormal
- nếu tất cả các bit được đặt, giá trị là một trong hai
infinity
hoặc NaN
này có nghĩa là số mũ thường xuyên lớn nhất sẽ được mã hóa thông qua thiết lập tất cả các bit trừ thấp nhất, tương ứng với giá trị 2**k - 2
hoặc 2**(k-1) - 1
nếu bạn trừ chênh lệch.
Đối double
giá trị, k = 11
, tức là số mũ cao nhất sẽ được 1023
, vì vậy giá trị dấu chấm động lớn nhất là trật tự 2**1023
đó là khoảng 1E+308
.
Giá trị lớn nhất sẽ có
- bit dấu thiết lập để
0
- tất cả ngoại trừ các bit số mũ thấp nhất thiết lập để
1
- tất cả các bit phân đoạn thiết lập để
1
Bây giờ, có thể hiểu số ma thuật của chúng tôi hoạt động như thế nào:
-1.0
đã thiết lập dấu bit của nó, số mũ là thiên vị - tức là tất cả các bit nhưng cao nhất có mặt - và các phần phân đoạn là 0
~(-1.0)
chỉ có các bit số mũ cao nhất và tất cả các bit phân đoạn thiết
0.5
có bit dấu và phần phân đoạn của 0
; số mũ sẽ là độ lệch trừ 1
, nghĩa là tất cả trừ các bit số mũ cao nhất và thấp nhất sẽ có mặt là
Khi chúng ta kết hợp hai giá trị này theo lôgic hoặc, chúng ta sẽ lấy mẫu bit chúng ta muốn.
Các tính toán làm việc cho x86 80-bit giá trị chính xác mở rộng (aka long double
) là tốt, nhưng các bit-twiddling phải được thực hiện byte-khôn ngoan như không có kiểu dữ liệu integer đủ lớn để giữ giá trị trên 32 phần cứng bit.
Độ lệch thực sự không bắt buộc phải là 2**(k-1) - 1
- nó sẽ hoạt động cho một thiên hướng tùy ý miễn là nó là lẻ. Độ lệch phải là số lẻ vì nếu không, các mẫu bit cho số mũ của 1.0
và 0.5
sẽ khác ở các vị trí khác so với bit thấp nhất.
Nếu cơ số b
(còn gọi là cơ số) của loại dấu phẩy động không phải là 2
, bạn phải sử dụng b**(-1)
thay vì 0.5 = 2**(-1)
.
Nếu giá trị số mũ lớn nhất không phải là máy chủ lưu trữ, hãy sử dụng 1.0
thay vì 0.5
. Điều này sẽ làm việc bất kể cơ sở hoặc thiên vị (có nghĩa là nó không còn bị giới hạn các giá trị lẻ). Sự khác biệt trong việc sử dụng 1.0
là bit số mũ thấp nhất sẽ không bị xóa.
Để tóm tắt:
~(-1.0) | 0.5
làm việc miễn là radix là 2
, độ sai lệch là số lẻ và số mũ cao nhất được dành riêng.
~(-1.0) | 1.0
hoạt động cho bất kỳ cơ số hoặc độ lệch nào miễn là số mũ cao nhất không được đặt trước.
tại sao bạn nghĩ điều đó có nghĩa là tránh float.h? tôi nghĩ rằng nó muốn bạn sử dụng một số chức năng như ldexp, FLT_RADIX (hoặc bất cứ thứ gì được gọi) và các công cụ –
Vâng, vì float.h đã có các giá trị tối thiểu/tối đa có vẻ như gian lận trong trường hợp này. Nhưng vâng, có một số hằng số khác được định nghĩa trong float.h có thể được sử dụng để xác định phạm vi thực tế. – Ree