Floating point number là gì

      115

Xác định nếu đối số dấu phẩy động đã cho là bình thường, tức là không phải là 0, hàm dưới, vô hạn, cũng không phải NaN.

Bạn đang xem: Floating point number là gì

Một số bằng 0, vô hạn hoặc NaN là rõ ràng ý nghĩa của nó. Nhưng nó cũng cho biết điều huyền bí. Khi nào một số là ẩn số?


Kết quả đầu tiên của google cho thấy nó chỉ là một từ đồng nghĩa với một từ không bình thường: en.wikipedia.org/wiki/Denormal_number
Xem câu hỏi này để thảo luận chuyên sâu về các mệnh giá và xử lý chúng: stackoverflow.com/questions/9314534/…

Trong tiêu chuẩn IEEE754, số dấu phẩy động được biểu diễn dưới dạng ký hiệu khoa học nhị phân, x = M × 2 e . Ở đây M là phần định trịesố mũ . Về mặt toán học, bạn luôn có thể chọn số mũ sao cho 1 ≤ M e phút . Những con số này là những subnormals hoặc denormals .

Trên thực tế, phần định trị được lưu trữ mà không có số 1 đứng đầu, vì luôn có số 1 đứng đầu, ngoại trừ các số siêu thường (và số 0). Do đó, cách giải thích là nếu số mũ không cực tiểu, thì có một số ẩn dẫn đầu là 1, và nếu số mũ là cực tiểu thì không có, và số là hàm số phụ

*) Tổng quát hơn, 1 ≤ M


— Kerrek SB nguồn
Bạn đang nói isnomallà truenếu 8 bit đều bằng 0 và falsengược lại?
— Pacerier
"được lưu trữ" hay được diễn giải?
— Pacerier
Pacerier: "storage": Nó được lưu trữ mà không có số 1 ở đầu, ví dụ như 001010, và được hiểu là 1.001010.
— Kerrek SB,
Có rõ emin được đề cập trong: `` e min không? `` (Tôi hy vọng nỗ lực định dạng của tôi thành công) ..
— Razzle
82

Kiến thức cơ bản về IEEE 754

Trước tiên, chúng ta hãy xem xét những điều cơ bản của IEEE 754 số được tổ chức.

Chúng tôi sẽ tập trung vào độ chính xác duy nhất (32-bit), nhưng mọi thứ có thể được tổng quát hóa ngay lập tức sang các phân khu khác.

Định dạng là:

1 bit: dấu8 bit: số mũ23 bit: phân số

Hoặc nếu bạn thích hình ảnh:

*

Nguồn .

Dấu hiệu rất đơn giản: 0 là tích cực và 1 là tiêu cực, kết thúc câu chuyện.

Số mũ dài 8 bit và do đó nó nằm trong khoảng từ 0 đến 255.

Số mũ được gọi là chệch vì nó có phần bù -127, ví dụ:

0 == special case: zero or subnormal, explained below 1 == 2 ^ -126 ...125 == 2 ^ -2126 == 2 ^ -1127 == 2 ^ 0128 == 2 ^ 1129 == 2 ^ 2 ...254 == 2 ^ 127255 == special case: infinity and NaNQuy ước bit hàng đầu

Trong khi thiết kế IEEE 754, các kỹ sư nhận thấy rằng tất cả các số, ngoại trừ 0.0, đều có một 1số nhị phân là chữ số đầu tiên. Ví dụ:

25.0 == (binary) 11001 == 1.1001 * 2^4 0.625 == (binary) 0.101 == 1.01 * 2^-1cả hai đều bắt đầu với 1.phần khó chịu đó .

Do đó, sẽ rất lãng phí nếu để chữ số đó chiếm một bit chính xác gần như mỗi số.

Vì lý do này, họ đã tạo ra "quy ước bit hàng đầu":

luôn giả định rằng số bắt đầu bằng một

Nhưng sau đó làm thế nào để đối phó với 0.0? Chà, họ quyết định tạo một ngoại lệ:

nếu số mũ là 0và phân số là 0sau đó số đại diện cho cộng hoặc trừ 0.0

để các byte 00 00 00 00cũng đại diện 0.0, trông đẹp.

Nếu chúng ta chỉ xem xét các quy tắc này, thì số khác 0 nhỏ nhất có thể được biểu diễn sẽ là:

số mũ: 0phân số: 1

trông giống như thế này trong một phân số hex do quy ước bit hàng đầu:

1.000002 * 2 ^ (-127)ở đâu .000002là 22 số 0 với một 1ở cuối.

Chúng tôi không thể lấy fraction = 0, nếu không con số đó sẽ là 0.0.

Nhưng rồi các kỹ sư, những người cũng có óc thẩm mỹ nhạy bén, đã nghĩ: điều đó không xấu sao? Rằng chúng ta nhảy từ thẳng 0.0đến một cái gì đó thậm chí không phải là lũy thừa thích hợp của 2? Bằng cách nào đó chúng ta không thể đại diện cho những con số thậm chí còn nhỏ hơn?

Các con số bất thường

Các kỹ sư vò đầu bứt tai một lúc, và trở lại, như thường lệ, với một ý tưởng hay khác. Điều gì sẽ xảy ra nếu chúng tôi tạo một quy tắc mới:

Nếu số mũ là 0, thì:

bit đầu tiên trở thành 0số mũ được sửa thành -126 (không phải -127 như thể chúng ta không có ngoại lệ này)

Những con số như vậy được gọi là số siêu thường (hoặc số không bình thường là từ đồng nghĩa).

Quy tắc này ngay lập tức ngụ ý rằng con số như vậy:

số mũ: 0phân số: 0

vẫn còn 0.0, đó là một loại thanh lịch vì nó có nghĩa là một quy tắc ít hơn để theo dõi.

Vì vậy, 0.0thực sự là một số siêu thường theo định nghĩa của chúng tôi!

Khi đó, với quy tắc mới này, số không chuẩn nhỏ nhất là:

số mũ: 1 (0 sẽ là ẩn số)phân số: 0

đại diện:

1.0 * 2 ^ (-126)Khi đó, số bậc ba lớn nhất là:

số mũ: 0phân số: 0x7FFFFF (23 bit 1)

bằng:

0.FFFFFE * 2 ^ (-126)nơi .FFFFFEmột lần nữa 23 bit một bên phải dấu chấm.

Con số này khá gần với con số không bình thường nhỏ nhất, nghe có vẻ lành mạnh.

Và số phụ nhỏ nhất khác 0 là:

số mũ: 0phân số: 1

bằng:

0.000002 * 2 ^ (-126)trông cũng khá gần với 0.0!

Không thể tìm thấy bất kỳ cách hợp lý nào để biểu thị những con số nhỏ hơn thế, các kỹ sư đã rất vui và quay trở lại xem ảnh mèo trực tuyến, hoặc bất cứ điều gì họ đã làm vào những năm 70.

Như bạn có thể thấy, các số siêu thường có sự cân bằng giữa độ chính xác và độ dài biểu diễn.

Như ví dụ cực đoan nhất, hàm phụ nhỏ nhất khác 0:

0.000002 * 2 ^ (-126)về cơ bản có độ chính xác của một bit thay vì 32 bit. Ví dụ, nếu chúng ta chia nó cho hai:

0.000002 * 2 ^ (-126) / 2chúng tôi thực sự tiếp cận 0.0chính xác!

Hình dung

Luôn luôn là một ý tưởng hay khi có trực giác hình học về những gì chúng ta học được, vì vậy hãy tiếp tục.

Nếu chúng ta vẽ biểu đồ IEEE 754 số dấu phẩy động trên một dòng cho mỗi số mũ đã cho, nó trông giống như sau:

+---+-------+---------------+-------------------------------+exponent |126| 127 | 128 | 129 | +---+-------+---------------+-------------------------------+ | | | | | v v v v v -------------------------------------------------------------floats ***** * * * * * * * * * * * * ------------------------------------------------------------- ^ ^ ^ ^ ^ | | | | | 0.5 1.0 2.0 4.0 8.0Từ đó chúng ta có thể thấy rằng:

đối với mỗi số mũ, không có sự chồng chéo giữa các số được biểu diễnvới mỗi số mũ, chúng ta có cùng một số 2 ^ 32 trong số các số (ở đây được biểu thị bằng 4 *)trong mỗi số mũ, các điểm cách đều nhausố mũ lớn hơn bao gồm các phạm vi lớn hơn, nhưng với các điểm trải rộng hơn

Bây giờ, hãy đưa nó xuống hết số mũ 0.

Nếu không có subnormals, theo giả thuyết, nó sẽ giống như sau:

+---+---+-------+---------------+-------------------------------+exponent | ? | 0 | 1 | 2 | 3 | +---+---+-------+---------------+-------------------------------+ | | | | | | v v v v v v -----------------------------------------------------------------floats * **** * * * * * * * * * * * * ----------------------------------------------------------------- ^ ^ ^ ^ ^ ^ | | | | | | 0 | 2^-126 2^-125 2^-124 2^-123 | 2^-127Với subnormals, nó trông như thế này:

+-------+-------+---------------+-------------------------------+exponent | 0 | 1 | 2 | 3 | +-------+-------+---------------+-------------------------------+ | | | | | v v v v v -----------------------------------------------------------------floats * * * * * * * * * * * * * * * * * ----------------------------------------------------------------- ^ ^ ^ ^ ^ ^ | | | | | | 0 | 2^-126 2^-125 2^-124 2^-123 | 2^-127Bằng cách so sánh hai biểu đồ, chúng tôi thấy rằng:

số phụ nhân đôi độ dài của phạm vi số mũ 0, từ <2^-127, 2^-126)đến<0, 2^-126)

Khoảng trống giữa các phao trong phạm vi công thường giống như đối với <0, 2^-126).

Xem thêm: Nguồn Gốc Của Ngày 1/4 Là Ngày Gì, 1/4 Là Ngày Gì

phạm vi <2^-127, 2^-126)có một nửa số điểm mà nó sẽ có nếu không có các đại lượng con.

Một nửa số điểm đó sẽ lấp đầy nửa còn lại của phạm vi.

phạm vi <0, 2^-127)có một số điểm với subnormals, nhưng không có điểm nào không có.

Thiếu điểm <0, 2^-127)này không được thanh lịch cho lắm, và là lý do chính để các subnormals tồn tại!

vì các điểm cách đều nhau:

phạm vi <2^-128, 2^-127)có một nửa số điểm hơn <2^-127, 2^-126)- <2^-129, 2^-128)có một nửa số điểm hơn<2^-128, 2^-127)và như thế

Đây là ý của chúng tôi khi nói rằng subnormals là sự cân bằng giữa kích thước và độ chính xác.

Ví dụ Runnable C

Bây giờ hãy chơi với một số mã thực tế để xác minh lý thuyết của chúng tôi.

Trong hầu hết các máy hiện tại và máy để bàn, C floatđại diện cho các số dấu phẩy động IEEE 754 chính xác duy nhất.

Đây là trường hợp cụ thể đối với máy tính xách tay Lenovo P51 chạy Ubuntu 18.04 amd64 của tôi.

Với giả định đó, tất cả các xác nhận được chuyển vào chương trình sau:

subnormal.c

#if __STDC_VERSION__ #error C11 required#endif#ifndef __STDC_IEC_559__#error IEEE 754 not implemented#endif#include #include /* FLT_HAS_SUBNORM */#include #include /* isnormal */#include #include #if FLT_HAS_SUBNORM != 1#error float does not have subnormal numbers#endiftypedef struct { uint32_t sign, exponent, fraction;} Float32;Float32 float32_from_float(float f) { uint32_t bytes; Float32 float32; bytes = *(uint32_t*)&f; float32.fraction = bytes & 0x007FFFFF; bytes >>= 23; float32.exponent = bytes & 0x000000FF; bytes >>= 8; float32.sign = bytes & 0x000000001; bytes >>= 1; return float32;}float float_from_bytes( uint32_t sign, uint32_t exponent, uint32_t fraction) { uint32_t bytes; bytes = 0; bytes |= sign; bytes 8; bytes |= exponent; bytes 23; bytes |= fraction; return *(float*)&bytes;}int float32_equal( float f, uint32_t sign, uint32_t exponent, uint32_t fraction) { Float32 float32; float32 = float32_from_float(f); return (float32.sign == sign) && (float32.exponent == exponent) && (float32.fraction == fraction) ;}void float32_print(float f) { Float32 float32 = float32_from_float(f); printf( "%" PRIu32 " %" PRIu32 " %" PRIu32 "\n", float32.sign, float32.exponent, float32.fraction );}int main(void) { /* Basic examples. */ assert(float32_equal(0.5f, 0, 126, 0)); assert(float32_equal(1.0f, 0, 127, 0)); assert(float32_equal(2.0f, 0, 128, 0)); assert(isnormal(0.5f)); assert(isnormal(1.0f)); assert(isnormal(2.0f)); /* Quick review of C hex floating point literals. */ assert(0.5f == 0x1.0p-1f); assert(1.0f == 0x1.0p0f); assert(2.0f == 0x1.0p1f); /* Sign bit. */ assert(float32_equal(-0.5f, 1, 126, 0)); assert(float32_equal(-1.0f, 1, 127, 0)); assert(float32_equal(-2.0f, 1, 128, 0)); assert(isnormal(-0.5f)); assert(isnormal(-1.0f)); assert(isnormal(-2.0f)); /* The special case of 0.0 and -0.0. */ assert(float32_equal( 0.0f, 0, 0, 0)); assert(float32_equal(-0.0f, 1, 0, 0)); assert(!isnormal( 0.0f)); assert(!isnormal(-0.0f)); assert(0.0f == -0.0f); /* ANSI C defines FLT_MIN as the smallest non-subnormal number. */ assert(FLT_MIN == 0x1.0p-126f); assert(float32_equal(FLT_MIN, 0, 1, 0)); assert(isnormal(FLT_MIN)); /* The largest subnormal number. */ float largest_subnormal = float_from_bytes(0, 0, 0x7FFFFF); assert(largest_subnormal == 0x0.FFFFFEp-126f); assert(largest_subnormal /* The smallest non-zero subnormal number. */ float smallest_subnormal = float_from_bytes(0, 0, 1); assert(smallest_subnormal == 0x0.000002p-126f); assert(0.0f return EXIT_SUCCESS;}GitHub ngược dòng .

Biên dịch và chạy với:

gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c./subnormal.outC ++

Ngoài việc hiển thị tất cả các API của C, C ++ cũng cho thấy một số chức năng phụ liên quan đến chức năng phụ không có sẵn trong C , ví dụ:

Trong C ++, toàn bộ API được tạo khuôn mẫu cho từng loại dấu phẩy động và đẹp hơn nhiều.

Triển khai

x86_64 và ARMv8 tích hợp IEEE 754 trực tiếp trên phần cứng, mà mã C dịch sang.

Subnormals dường như kém nhanh hơn bình thường trong các triển khai nhất định: Tại sao việc thay đổi 0,1f thành 0 lại làm chậm hiệu suất đi 10 lần? Điều này được đề cập trong sách hướng dẫn ARM, hãy xem phần "chi tiết về ARMv8" của câu trả lời này.

Chi tiết về ARMv8

Sách hướng dẫn tham khảo kiến ​​trúc ARM Sổ tay hướng dẫn sử dụng ARMv8 DDI 0487C.a A1.5.4 "Flush-to-zero" mô tả một chế độ có thể định cấu hình trong đó các khẩu súng con được làm tròn thành 0 để cải thiện hiệu suất:

Hiệu suất của quá trình xử lý dấu phẩy động có thể bị giảm khi thực hiện các phép tính liên quan đến các số không chuẩn hóa và ngoại lệ Dòng chảy dưới. Trong nhiều thuật toán, hiệu suất này có thể được phục hồi mà không ảnh hưởng đáng kể đến độ chính xác của kết quả cuối cùng, bằng cách thay thế các toán hạng không chuẩn hóa và kết quả trung gian bằng các số không. Để cho phép tối ưu hóa này, triển khai dấu phẩy động ARM cho phép sử dụng chế độ Flush-to-zero cho các định dạng dấu phẩy động khác nhau như sau:

Đối với AArch64:

Nếu FPCR.FZ==1, thì chế độ Flush-to-Zero được sử dụng cho tất cả các đầu vào và đầu ra chính xác đơn và chính xác kép của tất cả các lệnh.

Nếu FPCR.FZ16==1, thì chế độ Flush-to-Zero được sử dụng cho tất cả các đầu vào và đầu ra của Half-Precision của lệnh dấu phẩy động, ngoại trừ: —Chuyển đổi giữa số Half-Precision và Single-Precision. — Chuyển đổi giữa Half-Precision và Double-Precision những con số.

A1.5.2 "Tiêu chuẩn dấu phẩy động và thuật ngữ" Bảng A1-3 "Thuật ngữ dấu phẩy động" xác nhận rằng ký hiệu con và ký hiệu là từ đồng nghĩa:

This manual IEEE 754-2008------------------------- -------------<...>Denormal, or denormalized SubnormalC5.2.7 "FPCR, Thanh ghi điều khiển dấu chấm động" mô tả cách ARMv8 có thể tùy chọn nâng cao các ngoại lệ hoặc đặt các bit cờ bất cứ khi nào đầu vào của một phép toán dấu chấm động là không bình thường:

FPCR.IDE, bit <15> Đầu vào Kích hoạt bẫy ngoại lệ dấu phẩy động bất thường. Giá trị có thể là:

0b0 Đã chọn xử lý ngoại lệ chưa được gói. Nếu ngoại lệ dấu chấm động xảy ra thì bit FPSR.IDC được đặt thành 1.

0b1 Đã chọn xử lý ngoại lệ bị mắc kẹt. Nếu ngoại lệ dấu phẩy động xảy ra, PE không cập nhật bit FPSR.IDC. Phần mềm xử lý bẫy có thể quyết định có đặt bit FPSR.IDC thành 1 hay không.

D12.2.88 "MVFR1_EL1, AArch32 Media and VFP Feature Register 1" cho thấy rằng trên thực tế, hỗ trợ không bình thường là hoàn toàn tùy chọn và cung cấp một chút để phát hiện xem có hỗ trợ không:

FPFtZ, bit <3: 0>

Chuyển sang chế độ Zero. Cho biết việc triển khai dấu phẩy động chỉ cung cấp hỗ trợ cho chế độ hoạt động Flush-to-Zero. Giá trị xác định là:

0b0000 Không được triển khai hoặc phần cứng chỉ hỗ trợ chế độ hoạt động Flush-to-Zero.

0b0001 Phần cứng hỗ trợ số học không chuẩn hóa đầy đủ.

Tất cả các giá trị khác được bảo lưu.

Trong ARMv8-A, các giá trị được phép là 0b0000 và 0b0001.

Điều này cho thấy rằng khi các toán tử con không được triển khai, các triển khai chỉ hoàn nguyên về 0.