2013-08-06 70 views
5

Đây là một câu hỏi rất đơn giản, nhưng là một câu hỏi quan trọng vì nó ảnh hưởng rất lớn đến toàn bộ dự án của tôi.Cắt một đôi thành phao trong C

Giả sử tôi có snipet mã sau:

unsigned int x = 0xffffffff; 
float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 

Tôi mong rằng f có cái gì đó như 0,99999, nhưng thay vào đó, nó vòng lên tới 1, vì nó xấp xỉ gần float. Điều đó không tốt vì tôi cần float giá trị trong khoảng [0,1], không phải [0,1]. Tôi chắc chắn nó là một cái gì đó đơn giản, nhưng tôi đánh giá cao một số trợ giúp.

Trả lời

0

Giải pháp cuối cùng của tôi là chỉ thu nhỏ kích thước của hệ số không đổi.Đó có lẽ là giải pháp tốt nhất vì không có điểm nhân đôi nào. Độ chính xác không được nhìn thấy sau khi chuyển đổi thành phao.

nên 2.328306436538696e-010 đã được đổi thành 2.3283063

3

Giá trị ở trên là double vòng đến 1 hoặc nhiều hơn khi được chuyển đổi thành float trong chế độ làm tròn IEEE 754 mặc định là 0x1.ffffffp-1 (trong ký hiệu thập lục phân của C99).

lựa chọn của bạn là:

  1. biến các chế độ làm tròn FPU để tròn xuống trước khi chuyển đổi, hoặc
  2. nhân với (0x1.ffffffp-1/0xffffffffp0) (cho hay phải mất một ULP) để khai thác đầy đủ chính xác đơn khoảng [ 0, 1) mà không nhận giá trị 1.0f.

Phương pháp 2 leads to use the constant0x1.ffffff01fffffp-33:

double factor = nextafter(0x1.ffffffp-1/0xffffffffp0, 0.0); 
unsigned int x = 0xffffffff; 
float f = (float)((double)x * factor); 
printf("factor:%a\nunrounded:%a\nresult:%a\n", factor, (double)x * factor, f); 

Prints:

factor:0x1.ffffff01fffffp-33 
unrounded:0x1.fffffefffffffp-1 
result:0x1.fffffep-1 
1

Không có nhiều bạn có thể làm - bạn int giữ 32 bit nhưng mantissa của một float giữ chỉ 24. Tròn sẽ xảy ra. Bạn có thể thay đổi chế độ làm tròn bộ xử lý để làm tròn thay vì gần nhất, nhưng điều đó sẽ gây ra một số tác dụng phụ mà bạn muốn tránh đặc biệt là nếu bạn không khôi phục chế độ làm tròn khi bạn hoàn tất.

Không có gì sai với công thức bạn đang sử dụng, nó tạo ra câu trả lời chính xác nhất có thể cho đầu vào đã cho. Chỉ có một trường hợp kết thúc mà không có một yêu cầu khó khăn.

if (f >= 1.0f) 
    f = 0.99999994f; 

0,999999940395355224609375 là giá trị gần nhất mà một phao IEEE-754 có thể mất mà không bị bằng 1.0: Không có gì sai với thử nghiệm đối với trường hợp chấm dứt cụ thể và thay thế nó bằng giá trị gần nhất mà đáp ứng yêu cầu là.

+1

Đây không phải là một câu trả lời hữu ích. Như các câu trả lời khác đã cho thấy (và họ đã chỉ ra cách), có những điều bạn có thể làm. –

+0

@EricPostpischil, nó không hữu ích như thế nào? Nó cung cấp một giải pháp làm việc cho vấn đề, mà không để lại một chế độ làm tròn có hiệu lực mà sẽ thay đổi tất cả các tính toán trung gian và sau đó. –

+0

Tuyên bố “Không có nhiều việc bạn có thể làm” là gây hiểu lầm và không cần thiết ngăn cản. Câu lệnh về các bit trong 'int' và' float' không liên quan; OP không mong đợi một bản đồ chính xác. Họ không yêu cầu tránh làm tròn, chỉ để kiểm soát nó. –

8

Trong C (từ C99), bạn có thể thay đổi hướng làm tròn với fesetround từ libm

#include <stdio.h> 
#include <fenv.h> 
int main() 
{ 
    #pragma STDC FENV_ACCESS ON 
    fesetround(FE_DOWNWARD); 
    // volatile -- uncomment for GNU gcc and whoever else doesn't support FENV 
    unsigned long x = 0xffffffff; 
    float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 
    printf("%.50f\n", f); 
} 

Tested với IBM XL, Sun Studio, kêu vang, GNU gcc. Điều này mang lại cho tôi 0.99999994039535522460937500000000000000000000000000 trong mọi trường hợp

+0

Đây có phải là hàm C++ 11 không? –

+0

@MarkB C99 chức năng, bao gồm trong C++ 11 – Cubbi

+0

@EricPostpischil cảm ơn đã chỉ ra, viết lại trong C – Cubbi

1

Bạn chỉ có thể cắt bớt giá trị thành độ chính xác tối đa (giữ 24 bit cao) và chia cho 2^24 để nhận giá trị gần nhất mà phao có thể biểu thị mà không được làm tròn thành 1;

unsigned int i = 0xffffffff; 
float value = (float)(i>>8)/(1<<24); 

printf("%.20f\n", value); 
printf("%a\n", value); 

>>> 0.99999994039535522461 
>>> 0x1.fffffep-1 
+0

Đây có thể là một cách tiếp cận tốt, nếu làm tròn mọi giá trị về 0 (không chỉ những giá trị gần 1) phù hợp với OP. Việc hack cho hình minh họa là không cần thiết; chúng ta có thể sử dụng trình định dạng định dạng '% a' để hiển thị các số dấu phẩy động theo cách minh họa bố cục của chúng. –

+0

@EricPostpischil Cảm ơn định dạng '% a', không biết về điều đó. –

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