Phiên bản nội tuyến của một hàm trả về giá trị khác với phiên bản không nội tuyến


85

Làm thế nào để hai phiên bản của cùng một hàm, chỉ khác nhau ở một phiên bản là nội tuyến và phiên bản kia thì không, trả về các giá trị khác nhau? Đây là một số mã tôi đã viết ngày hôm nay và tôi không chắc nó hoạt động như thế nào.

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

Tôi mong đợi tất cả các kết quả đầu ra bằng nhau 1, nhưng nó thực sự xuất ra điều này (g ++ 8.3.1, không có cờ):

1
0
1

thay vì

1
1
1

Chỉnh sửa: clang ++ 7.0.0 xuất ra:

0
0
0

và g ++ -Ofast này:

1
1
1

3
Bạn có thể vui lòng cung cấp trình biên dịch, tùy chọn trình biên dịch bạn đang sử dụng và máy gì không? Hoạt động tốt đối với tôi trên GCC 7.1 trên Windows.
Diodacus

31
Không phải ==lúc nào cũng khó đoán trước với các giá trị dấu phẩy động?
500 - Lỗi máy chủ nội bộ


2
Bạn đã đặt -Ofasttùy chọn cho phép tối ưu hóa như vậy chưa?
cmdLP

4
Trình biên dịch trả về cbrt(27.0)giá trị 0x0000000000000840trong khi thư viện chuẩn trả về 0x0100000000000840. Các đôi khác nhau ở số thứ 16 sau dấu phẩy. Hệ thống của tôi: Archlinux4.20 x64 gcc8.2.1 glibc2.28 Đã kiểm tra với cái này . Tự hỏi nếu gcc hoặc glibc là đúng.
KamilCuk

Câu trả lời:


73

Giải trình

Một số trình biên dịch (đặc biệt là GCC) sử dụng độ chính xác cao hơn khi đánh giá các biểu thức tại thời điểm biên dịch. Nếu một biểu thức chỉ phụ thuộc vào các đầu vào không đổi và các ký tự, nó có thể được đánh giá tại thời điểm biên dịch ngay cả khi biểu thức không được gán cho một biến constexpr. Điều này có xảy ra hay không phụ thuộc vào:

  • Sự phức tạp của biểu thức
  • Ngưỡng mà trình biên dịch sử dụng làm ngưỡng khi cố gắng thực hiện đánh giá thời gian biên dịch
  • Các phương pháp heuristics khác được sử dụng trong các trường hợp đặc biệt (chẳng hạn như khi clang trôi qua các vòng lặp)

Nếu một biểu thức được cung cấp rõ ràng, như trong trường hợp đầu tiên, nó có độ phức tạp thấp hơn và trình biên dịch có khả năng đánh giá nó tại thời điểm biên dịch.

Tương tự, nếu một hàm được đánh dấu nội tuyến, trình biên dịch có nhiều khả năng đánh giá nó tại thời điểm biên dịch vì các hàm nội tuyến nâng cao ngưỡng mà tại đó đánh giá có thể xảy ra.

Mức tối ưu hóa cao hơn cũng làm tăng ngưỡng này, như trong ví dụ -Ofast, trong đó tất cả các biểu thức đánh giá là true trên gcc do đánh giá thời gian biên dịch chính xác cao hơn.

Chúng ta có thể quan sát hành vi này ở đây trên trình khám phá trình biên dịch. Khi được biên dịch với -O1, chỉ hàm được đánh dấu nội tuyến mới được đánh giá tại thời điểm biên dịch, nhưng tại -O3, cả hai hàm đều được đánh giá tại thời điểm biên dịch.

NB: Trong các ví dụ về trình biên dịch, tôi sử dụng printfiostream thay vì nó làm giảm độ phức tạp của hàm chính, làm cho hiệu ứng rõ ràng hơn.

Chứng minh rằng điều inlineđó không ảnh hưởng đến đánh giá thời gian chạy

Chúng tôi có thể đảm bảo rằng không có biểu thức nào được đánh giá tại thời điểm biên dịch bằng cách lấy giá trị từ đầu vào chuẩn và khi chúng tôi làm điều này, cả 3 biểu thức đều trả về false như được minh họa tại đây: https://ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

Ngược lại với ví dụ này , trong đó chúng tôi sử dụng cài đặt trình biên dịch giống nhau nhưng cung cấp giá trị tại thời điểm biên dịch, dẫn đến đánh giá thời gian biên dịch có độ chính xác cao hơn.


22

Theo quan sát, việc sử dụng ==toán tử để so sánh các giá trị dấu chấm động đã dẫn đến các kết quả đầu ra khác nhau với các trình biên dịch khác nhau và ở các mức tối ưu hóa khác nhau.

Một cách tốt để so sánh các giá trị dấu phẩy động là kiểm tra dung sai tương đối được nêu trong bài viết: Đã xem lại dung sai dấu chấm động .

Đầu tiên chúng tôi tính toán giá trị Epsilon( dung sai tương đối ) trong trường hợp này sẽ là:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

Và sau đó sử dụng nó trong cả hàm nội tuyến và không nội tuyến theo cách này:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

Các chức năng bây giờ là:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

Bây giờ đầu ra sẽ như mong đợi ( [1 1 1]) với các trình biên dịch khác nhau và ở các mức tối ưu hóa khác nhau.

Bản thử trực tiếp


Mục đích của max()cuộc gọi là gì? Theo định nghĩa, floor(x)nhỏ hơn hoặc bằng x, vì vậy max(x, floor(x))sẽ luôn luôn bằng x.
Ken Thomases,

@KenThomases: Trong trường hợp cụ thể này, khi một đối số maxchỉ là đối số của đối số floorkia, nó không bắt buộc. Nhưng tôi đã xem xét một trường hợp chung trong đó các đối số maxcó thể là các giá trị hoặc biểu thức độc lập với nhau.
PW

Không nên operator==(double, double)làm chính xác điều đó, hãy kiểm tra sự khác biệt nhỏ hơn so với một epsilon được chia tỷ lệ? Khoảng 90% câu hỏi liên quan đến dấu chấm động trên SO sẽ không tồn tại khi đó.
Peter - Phục hồi Monica

Tôi nghĩ sẽ tốt hơn nếu người dùng chỉ định Epsilongiá trị tùy thuộc vào yêu cầu cụ thể của họ.
PW
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.