2012-09-01 13 views
8

Xét đoạn mã sau:Tính không thống nhất tính toán Sine trong Visual C++ 2012?

// Filename fputest.cpp 

#include <cmath> 
#include <cstdio> 

int main() 
{ 
    double x; 
    *(__int64 *) &x = 0xc01448ec3aaa278di64; // -5.0712136427263319 
    double sine1 = sin(x); 
    printf("%016llX\n", sine1); 
    double sine2; 
    __asm { 
    fld x 
    fsin 
    fstp sine2 
    } 
    printf("%016llX\n", sine2); 
    return 0; 
} 

Khi biên soạn với Visual C++ 2012 (cl fputest.cpp) và chương trình được thực thi, kết quả như sau:

3FEDF640D8D36174 
3FEDF640D8D36175 

Câu hỏi:

  • Tại sao hai giá trị này lại khác nhau?
  • Có thể phát hành một số tùy chọn trình biên dịch sao cho các giá trị sin được tính toán sẽ giống hệt nhau không?
+1

Một hoạt động có thể diễn ra hoàn toàn trong thanh ghi điểm động. Cái còn lại đòi hỏi sự mất chính xác khi thanh ghi 80 bit được ghi vào địa chỉ bộ nhớ 64 bit. Tài liệu FSTP nói rằng "khi lưu trữ giá trị trong bộ nhớ, giá trị được chuyển đổi thành định dạng đơn hoặc kép." –

+1

Cách tiếp cận 'fsin' sử dụng FPU x87 với độ chính xác 80 bit, việc thực hiện' sin' trong MSVC (tôi đang sử dụng 2010) dường như sử dụng SSE với thanh ghi 128 x bit *. (Ngoài ra, hãy xem [câu hỏi này] (http://stackoverflow.com/questions/2284860/how-does-c-compute-sin-and-other-math-functions).) – DCoder

Trả lời

9

Vấn đề này không phải do chuyển đổi từ dài gấp đôi thành gấp đôi. Nó có thể là do thiếu chính xác trong thói quen sin trong thư viện toán học.

Hướng dẫn fsin được chỉ định để tạo kết quả trong vòng 1 ULP (ở định dạng kép dài) cho các toán hạng trong phạm vi của nó (theo Hướng dẫn của nhà phát triển phần mềm kiến ​​trúc Intel 64 và IA-32, tháng 10 năm 2011, tập 1, 8.3.10), ở chế độ từ tròn đến gần nhất. Trên Intel Core i7, fsin giá trị của người hỏi, −5.07121364272633190495298549649305641651153564453125 hoặc -0x1.448ec3aaa278dp + 2, tạo ra 0xe.fb206c69b0ba402p-4. Chúng ta có thể dễ dàng nhìn thấy từ hệ thập lục phân này mà 11 bit cuối cùng là 100 0000 0010. Đó là các bit sẽ được làm tròn khi chuyển đổi từ dài gấp đôi. Nếu chúng lớn hơn 100 0000 0000, số sẽ được làm tròn lên. Chúng lớn hơn. Do đó, kết quả của việc chuyển đổi giá trị gấp đôi này thành gấp đôi là 0xe.fb206c69b0ba8p-4, bằng 0x1.df640d8d36175p-1 và 0,93631021832247418590355891865328885614871978759765625. Cũng lưu ý rằng ngay cả khi kết quả là một ULP thấp hơn, 11 bit cuối cùng vẫn sẽ lớn hơn 100 0000 0000 và vẫn sẽ tròn lên. Do đó kết quả này không nên thay đổi trên CPU Intel phù hợp với tài liệu trên.

So sánh điều này để tính toán sin có độ chính xác gấp đôi, sử dụng quy trình sin lý tưởng để tạo kết quả được làm tròn chính xác. Sin của giá trị là khoảng 0,93631021832247413051857150785044253634581268961333520518023697738674775240815140702992025520721336793516756640679315765619707343171517531053811196321335899848286682535203710849065933755262347468763562 (được tính bằng Maple 10). Đôi gần nhất với điều này là 0x1.df640d8d36175p-1. Đó là cùng một giá trị chúng tôi thu được bằng cách chuyển đổi kết quả fsin thành gấp đôi.

Do đó, sự khác biệt không được gây ra bởi việc chuyển đổi dài gấp đôi thành gấp đôi; chuyển đổi kết quả dài gấp đôi fsin thành gấp đôi tạo ra chính xác kết quả tương tự như lý tưởng chính xác gấp đôi chính xác sin.

Chúng tôi không có đặc điểm kỹ thuật về độ chính xác của quy trình sin được sử dụng bởi gói Visual Studio của người hỏi. Trong thư viện thương mại, cho phép lỗi của 1 ULP hoặc một số ULP là phổ biến. Quan sát cách đóng sine là một điểm mà giá trị chính xác kép được làm tròn: Nó là .498864 ULP (độ chính xác gấp đôi ULP) cách xa gấp đôi, do đó, nó là 0,001136 ULP cách xa điểm làm tròn thay đổi. Do đó, ngay cả một sự thiếu chính xác rất nhỏ trong quy trình sin sẽ khiến nó trả về 0x1.df640d8d36174p-1 thay vì 0x1.df640d8d36175p-1 gần hơn.

Vì vậy, tôi phỏng đoán nguồn sai lệch là một sự thiếu chính xác rất nhỏ trong quy trình sin.

1

(Lưu ý:..! Như đã đề cập trong các ý kiến, điều này không làm việc trên VC2012 tôi đã để lại nó ở đây để biết thông tin nói chung tôi sẽ không khuyên bạn nên dựa vào bất cứ điều gì mà phụ thuộc vào mức độ tối ưu hóa anyway)

Tôi không có VS2012, nhưng trên trình biên dịch VS2010 bạn có thể chỉ định /fp:fast trên dòng lệnh và sau đó tôi nhận được kết quả tương tự. Điều này làm cho trình biên dịch tạo ra mã "nhanh" mà không nhất thiết phải tuân theo các yêu cầu làm tròn và các quy tắc trong C++ nhưng phù hợp với phép tính ngôn ngữ lắp ráp của bạn.

Tôi không thể thử điều này trong VS2012 nhưng tôi tưởng tượng nó có cùng một tùy chọn.

Điều này dường như chỉ hoạt động trong một bản dựng được tối ưu hóa quá với /Ox làm tùy chọn.

1

Xem Why is cos(x) != cos(y) even though x == y?

Như David mentioned in the comment, sự khác biệt bắt nguồn từ việc di chuyển dữ liệu trong một FP đăng ký đến một vị trí bộ nhớ (đăng ký/ram) có kích thước khác nhau. Và nó không phải lúc nào cũng là nhiệm vụ; thậm chí một hoạt động điểm nổi lân cận khác cũng có thể đủ để xóa sổ đăng ký FP, hiển thị bất kỳ nỗ lực nào để đảm bảo một giá trị cụ thể vô ích. Nếu bạn cần so sánh, bạn có thể giảm thiểu một số điều này bằng cách buộc tất cả kết quả vào một vị trí bộ nhớ như sau:

float F1 = sin(a); float F2 = sin(b); if (F1 == F2) 

Tuy nhiên, điều này có thể không hoạt động. Cách tiếp cận tốt nhất là chấp nhận rằng bất kỳ thao tác dấu chấm động nào sẽ chỉ 'chủ yếu là chính xác' và từ quan điểm của lập trình viên, lỗi này sẽ không thể đoán trước được và ngẫu nhiên, ngay cả khi thao tác tương tự được thực hiện liên tục. Thay vì

if (F1 == F2) 

bạn nên sử dụng một cái gì đó để tác động của

if (isArbitrarilyClose(F1, F2)) 

hoặc

if (absf(F1 - F2) <= n) 

nơi n là một con số nhỏ.

+2

[Câu trả lời của tôi] (http://stackoverflow.com/a/12227672/298225) chứng minh điều này là không chính xác: Trong trường hợp này, làm tròn kết quả 'fsin' gấp đôi dài không tạo ra giá trị sai hoặc giá trị khác với kết quả của phép tính được làm tròn một cách chính xác' sin' trực tiếp. –

+0

Tính toán dấu chấm động * có thể * nhất quán trong một số hoặc thậm chí hầu hết các trường hợp; tuy nhiên, bạn phải cực kỳ cẩn thận và thậm chí sau đó trình biên dịch vẫn có thể cản trở nỗ lực của bạn. Vẫn còn tốt nhất để chuẩn bị cho điều tồi tệ nhất. – Ghost2

0

Trình tạo mã trong VS2012 đã được thay đổi đáng kể để hỗ trợ automatic vectorization. Một phần của sự thay đổi đó là toán tử dấu chấm động x86 bây giờ được thực hiện trong SSE2 và không còn sử dụng FPU nữa, cần thiết bởi vì mã FPU không thể được vector hóa. SSE2 tính toán với độ chính xác 64 bit thay vì độ chính xác 80 bit, cho tỷ lệ chênh lệch tốt rằng kết quả bị tắt một chút do tính năng làm tròn. Ngoài ra lý do mà @ J99 có thể nhận được kết quả nhất quán với/fp: nhanh trong VS2010, trình biên dịch của nó vẫn sử dụng FPU và/fp: nhanh chóng sử dụng kết quả FSIN trực tiếp.

Có rất nhiều sự cố trong tính năng này, hãy kiểm tra video của Jim Hogg tại url được liên kết để tìm hiểu cách tận dụng lợi thế của nó.

+2

Độ chính xác 64 bit không hàm ý phân phối 50% kết quả không chính xác. Các thói quen siêu việt như thế này thường tính các phần có cường độ thấp hơn của một đa thức gần đúng đầu tiên và thêm thành phần lớn nhất cuối cùng, tạo ra một kết quả có thể được làm tròn một cách chính xác rất nhiều thời gian. –