2009-02-08 16 views
5

Có thể tính toán phạm vi của các kiểu dữ liệu kép, đôi và dài theo cách di động mà không đọc float.h và sử dụng ANSI C không? Bởi di động, tôi có nghĩa là bao gồm những trường hợp khi máy mục tiêu không tuân theo tiêu chuẩn IEEE 754.Máy tính phạm vi của các kiểu dữ liệu điểm động

Tôi đang đọc sách K & R và bài tập 2-1 yêu cầu tôi "tính toán" chúng vì vậy tôi cho rằng điều đó có nghĩa là tránh float.h hoàn toàn bao gồm FLT_MIN, FLT_MAX, DBL_MIN và DBL_MAX (đọc các giá trị này trực tiếp sẽ chắc chắn không phân loại là "tính toán").

+0

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ụ –

+0

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

Trả lời

3

Rủi ro của câu trả lời thừa:

Không. Không có cách nào để tính toán phạm vi. Đó là lý do tại sao tiêu đề <float.h> được cung cấp - bởi vì không có cách di động để lấy được thông tin chứa trong đó.

+2

Đó là sự thật - nhưng có những tính toán sẽ làm việc cho một loạt các chương trình mã hóa! – Christoph

2

Bạn có thể thử tạo phao lớn hơn cho đến khi nó tràn.

+0

Đó là những gì tôi đã làm với các loại số nguyên - nhưng nó sẽ không mất một thời gian để tràn một đôi dài? – Ree

+0

Nếu bạn làm điều đó bằng tìm kiếm theo hàm mũ, nó sẽ tràn đủ nhanh. Bạn thậm chí có thể làm cho số phát triển nhanh hơn (ví dụ: tìm kiếm theo hàm mũ của số mũ tối đa so với tìm kiếm theo hàm mũ của giá trị lớn nhất) – jpalecek

4

Đối với 99,99% của tất cả các ứng dụng, bạn nên sử dụng IEEE 754 và sử dụng các hằng số được xác định trong <float.h>. Trong 0,01% khác, bạn sẽ làm việc với phần cứng rất chuyên dụng, và trong trường hợp đó, bạn nên biết những gì để sử dụng dựa trên phần cứng.

11

Có thể (ít nhất là cho IEEE 754 floatdouble 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.00.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.

+0

Nói "cho IEEE 754" phủ nhận tiền đề của câu hỏi. –

+1

@ Jonathan: tại 'ít nhất là' không có nghĩa là 'chỉ dành cho' - điều này phù hợp với mọi giá trị dấu phẩy động với độ lệch '2 ** (k-1) - 1' và số mũ dự trữ của '2 ** k - 1', bao gồm giá trị chính xác một nửa và bốn lần cũng như giá trị độ chính xác mở rộng 80 bit trên x86 – Christoph

+1

@ Jonathan: Làm cách nào khác bạn muốn 'tính toán' điều gì đó nếu bạn không hạn chế các sơ đồ mã hóa có thể? Có chỉ là không phải là một thuật toán mà sẽ làm việc cho tất cả các mã hóa! – Christoph

Các vấn đề liên quan