Làm thế nào chúng ta phải so sánh hai số nguyên?


8

Gần đây tôi đã viết một chương trình sắp xếp một mảng. Đối với nó, tôi cần phải viết một hàm so sánh, mà tôi sẽ chuyển vào nó. Hàm so sánh của tôi đã trả về 1 (nếu x> y), -1 (nếu x <y) hoặc 0 (nếu x = y). Tôi đã viết một hàm thông thường (Hàm 1) bằng các biểu thức điều kiện, nhưng tôi được khuyên viết khác (Hàm 2). Có tốt hơn để viết như vậy? Điều kiện Boolean sẽ luôn trả về 1 cho sự thật phải không?

Chức năng 1:

int Icmp(void* x, void* y)
{
    int a = *(int*)x;
    int b = *(int*)y;
    if (a > b)
        return 1;
    else if (a < b)
        return -1;
    else
        return 0;
}

Chức năng 2:

int Icmp(void* x, void* y)
{
    return (*(int*)x > * (int*)y) - (*(int*)x < *(int*)y);
}

5
Ưu điểm danh nghĩa của lần thứ hai là nó tránh được các bước nhảy có điều kiện và có thể hoạt động tốt hơn. Các ký hiệu được sử dụng là ghastly; các biến nên được địa phương hóa như trong lần đầu tiên và sau đó được sử dụng trong tính toán.
Jonathan Leffler

4
Cách thứ hai là một mẹo phổ biến để buộc nó không phân nhánh trên các máy tính ngu ngốc. Kết hợp khả năng đọc của người đầu tiên để có được tốt nhất của cả hai thế giới. Chỉnh sửa: tức là như Jonathan đã nói ở trên
Antti Haapala

8
Cả hai kỹ thuật làm việc. Đo sự khác biệt hiệu suất một cách cẩn thận. Có lẽ sẽ khó phát hiện. Thích sự rõ ràng khi sự khác biệt là biên.
Jonathan Leffler

1
Hỏi người đã khuyên bạn tại sao. Hỏi trong những tình huống những lợi thế đã nêu xảy ra. Hỏi lý do tại sao những siutations dự kiến ​​sẽ nổi bật cho bạn. Nếu bạn không nhận được câu trả lời thỏa mãn cho tất cả những câu hỏi đó, thì hãy tìm một cố vấn khác. Xem xét đo lường các lợi thế đã nêu, nếu có thể. Hãy tự xem xét tầm quan trọng của những lợi thế mà bạn nhận thức được, quan trọng nhất là "Tôi hiểu mã" và "Tôi nghĩ tất cả các đồng nghiệp của tôi hiểu mã tốt hơn". Sau đó tự quyết định.
Yunnosch

2
@ JoëlHecht nếu nó không phải là 1 thì nó không phải là trình biên dịch C. Tại sao bạn muốn sử dụng trình biên dịch không C để biên dịch mã C?!
Antti Haapala

Câu trả lời:


14

Cách ưa thích để viết mã không phân nhánh sẽ là sử dụng biến cục bộ cho toán hạng:

int icmp(const void *x, const void *y)
{
    int a = *(const int *)x;
    int b = *(const int *)y;
    return (a > b) - (a < b);
}

Biểu thức này là một thành ngữ phổ biến trong các hàm so sánh, và nếu được viết bằng cách sử dụng các biến thay vì các biểu tượng con trỏ tại chỗ, nó cũng khá dễ đọc.

Mã dựa trên thực tế là kết quả của một so sánh sử dụng >, <hoặc thậm chí ==là loại intvà 1 hoặc 0. Điều này được yêu cầu bởi tiêu chuẩn C - bất kỳ trình biên dịch nào tạo ra các giá trị như 42 hoặc -1 là theo định nghĩa không phải là trình biên dịch C .

Thật dễ dàng để thấy rằng tối đa. một trong a > bhoặc a < bcó thể là sự thật tại một thời điểm nhất định, và kết quả là một trong hai 1 - 0, 0 - 1hoặc 0 - 0.

Về lý do tại sao mã không phân nhánh - trong khi trình biên dịch có thể tạo cùng một mã chính xác cho cả hai hàm, chúng thường không. Ví dụ, GCCICC mới nhất dường như đều tạo ra một nhánh cho hàm đầu tiên trên x86-64, nhưng mã không phân nhánh với thực thi có điều kiện cho cái sau. Và với bất cứ ai nói rằng các chi nhánh không quan trọng, thì tôi giới thiệu bạn đến QA được bình chọn cao nhất từng có trên Stack Overflow .


Đây là cách chính xác để làm điều đó. Nó rất dễ đọc, mã máy được biên dịch giống hệt nhau và nó tránh các nhánh có lợi cho một số học bổ sung. Bộ dự báo nhánh có thể gây ra nhiều vấn đề về hiệu năng khi sắp xếp các mảng lớn đang ở trạng thái entropy cao. Điều này tránh được vấn đề đó.
3ch0

5
Chỉ cần lưu ý rằng sự vắng mặt của một phân tầng ifkhông có nghĩa là sẽ không có chi nhánh nào được tạo ra, cũng như nếu không có một phân tầng ifnào đó sẽ có một chi nhánh. Nó thực sự phụ thuộc vào việc trình biên dịch có thể tối ưu hóa chúng đi hay không.
G. Sliepen

@ G.Sliepen thực sự ICC sẽ hoàn toàn có khả năng tối ưu hóa chúng nhưng đã đi đến kết luận rằng chi nhánh có thể sẽ không được thực hiện bởi vì nó được viết bằng các nhánh ...
Antti Haapala

3

Có tốt hơn để viết như vậy?

Tôi sẽ nói không.

Đối với hiệu suất; hoặc nó không thành vấn đề (có khả năng cho các trình biên dịch hiện đại) hoặc nó không phải là một hàm riêng biệt (và nên được xây dựng thành mã được sử dụng để sắp xếp) hoặc bạn không nên sắp xếp tất cả (ví dụ: dữ liệu được sắp xếp khi tạo và không được sắp xếp sau khi tạo).

Để dễ đọc (bảo trì mã, cơ hội thấy lỗi trong phiên bản gốc, có nguy cơ giới thiệu lỗi sau) Tôi thích phiên bản gốc của bạn hơn; đặc biệt là khi làm việc trong một nhóm và đặc biệt là khi các thành viên khác trong nhóm quen thuộc hơn với 10 ngôn ngữ lập trình khác mà mỗi ngôn ngữ có quy tắc rất khác với C.

Đặc biệt; Tôi thích điều này (vì các phôi trong mã thực tế làm cho mọi thứ khó đọc hơn):

    int a = *(int*)x;
    int b = *(int*)y;

.. và tôi sẽ viết lại phần còn lại để trông như thế này:

    if (a > b) {
        return 1;
    }
    if (a < b) {
        return -1;
    }
    return 0;
}

.. hoặc trông như thế này:

    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
}

.. bởi vì elsekhông cần thiết sau a return; và bởi vì "nếu không có dấu ngoặc theo sau câu lệnh trên chính nó" sẽ tạo ra nguy cơ ai đó vô tình chèn một dòng mới mà không nhận ra và phá vỡ mọi thứ (ví dụ: xem https://dwheeler.com/essays/apple-goto- fail.html ).


0

Nếu bạn đang sử dụng hàm so sánh với qsort, hàm chỉ cần trả về các giá trị + ve, -ve hoặc zero.

Trong trường hợp đó, bạn chỉ có thể trừ các số

int Icmp(const void* x, const void* y)
{
    return (*(int*)x - *(int*)y);
}

Điều này thực sự hiệu quả hơn (a > b) - (a < b), nhưng vấn đề a - blà phép trừ có thể bị tràn. Đây là một biểu hiện xấu.
chmike

-2

Số nguyên

echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

Phao

echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

Dây

echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1

Câu hỏi là về cách so sánh số nguyên trong C. Câu trả lời của bạn rõ ràng không phải là mã C hợp lệ và do đó không cung cấp thông tin hữu ích cho người hỏi câu hỏi. Hãy chắc chắn rằng câu trả lời của bạn là về chủ đề.
G. Sliepen
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.