Một số dấu phẩy động bất thường là gì?


82

Trang tham chiếu isnormal () cho biết:

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.

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ố?


2
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
tenfour

10
Chưa hết, lượt truy cập thứ 2 trên Google (tìm kiếm “dấu phẩy động phụ”) chính là câu hỏi này.
Slipp D. Thompson,

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/…
hình

Câu trả lời:


78

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  <2. * Tuy nhiên, vì trong biểu diễn máy tính, số mũ chỉ có thể có một phạm vi hữu hạn, có một số số lớn hơn 0, nhưng nhỏ hơn 1,0 × 2 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  <  B   đối với bất kỳ ký hiệu khoa học cơ sở B nào .


Bạn đang nói isnomaltruenế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ểu1.001010.
Kerrek SB,

Có rõ emin được đề cập trong: `` e <sub> min </sub> 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ấu
  • 8 bit: số mũ
  • 23 bit: phân số

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

nhập mô tả hình ảnh ở đây

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 ^ -2
126 == 2 ^ -1
127 == 2 ^  0
128 == 2 ^  1
129 == 2 ^  2
    ...
254 == 2 ^ 127
255 == special case: infinity and NaN

Quy ướ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^-1

cả 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à 0
  • và phân số là 0
  • sau đó 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ũ: 0
  • phâ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 0
  • số 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ũ: 0
  • phâ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ũ: 0
  • phâ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ũ: 0
  • phâ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) / 2

chú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.0

Từ đó 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ễn
  • vớ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 nhau
  • số 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^-127

Vớ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^-127

Bằ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).

  • 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__ < 201112L
#error C11 required
#endif

#ifndef __STDC_IEC_559__
#error IEEE 754 not implemented
#endif

#include <assert.h>
#include <float.h> /* FLT_HAS_SUBNORM */
#include <inttypes.h>
#include <math.h> /* isnormal */
#include <stdlib.h>
#include <stdio.h>

#if FLT_HAS_SUBNORM != 1
#error float does not have subnormal numbers
#endif

typedef 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 < FLT_MIN);
    assert(!isnormal(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 < smallest_subnormal);
    assert(!isnormal(smallest_subnormal));

    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.out

C ++

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 <limits>, ví dụ:

  • denorm_min: Trả về giá trị âm dương tối thiểu của loại T

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   Subnormal

C5.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.

Infinity và NaN

Tò mò? Tôi đã viết một số điều tại:


1
Trích dẫn cho 'Trong khi thiết kế IEEE 754 ..'? Hoặc tốt hơn để bắt đầu câu bằng 'Được cho là'
Pacerier

@Pacerier Tôi không nghĩ rằng thực tế có thể sai :-) Có thể có lý do nào khác cho nó? Có thể điều này đã được biết trước đây, nhưng tôi nghĩ vậy là ổn.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

29

Từ http://blogs.oracle.com/d/entry/subnormal_numbers :

Có thể có nhiều cách biểu diễn cùng một số, sử dụng số thập phân làm ví dụ, số 0,1 có thể được biểu diễn dưới dạng 1 * 10 -1 hoặc 0,1 * 10 0 hoặc thậm chí 0,01 * 10. Tiêu chuẩn quy định rằng các số luôn được lưu trữ với bit đầu tiên là một. Trong số thập phân tương ứng với ví dụ 1 * 10-1.

Bây giờ, giả sử rằng số mũ thấp nhất có thể được biểu diễn là -100. Vì vậy, số nhỏ nhất có thể được biểu diễn ở dạng bình thường là 1 * 10 -100 . Tuy nhiên, nếu chúng ta nới lỏng ràng buộc rằng bit đầu phải là một, thì chúng ta thực sự có thể biểu diễn các số nhỏ hơn trong cùng một không gian. Lấy một ví dụ thập phân, chúng ta có thể biểu diễn 0,1 * 10 -100 . Đây được gọi là số siêu thường. Mục đích của việc có các số siêu thường là để làm phẳng khoảng cách giữa số bình thường nhỏ nhất và số 0.

Điều rất quan trọng là nhận ra rằng các số siêu thường được biểu diễn với độ chính xác kém hơn các số bình thường. Trên thực tế, họ đang kinh doanh giảm độ chính xác cho kích thước nhỏ hơn của họ. Do đó, các phép tính sử dụng số siêu thường sẽ không có độ chính xác như các phép tính trên số bình thường. Vì vậy, một ứng dụng thực hiện tính toán đáng kể trên các số siêu thường có lẽ đáng để nghiên cứu để xem liệu việc thay đổi tỷ lệ (tức là nhân các số với một số hệ số tỷ lệ) sẽ mang lại ít đại số con hơn và kết quả chính xác hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.