13

Nguồn câu trả lời của tôi trong:phận Integer 7

Is this expression correct in C preprocessor

tôi là một chút ra khỏi sở trường của tôi ở đây, và tôi đang cố gắng để hiểu làm thế nào các công trình này tối ưu hóa đặc biệt.

Như đã đề cập trong câu trả lời, gcc sẽ tối ưu hóa phân chia số nguyên 7 tới:

mov edx, -1840700269 
mov eax, edi 
imul edx 
lea eax, [rdx+rdi] 
sar eax, 2 
sar edi, 31 
sub eax, edi 

Những dịch trở lại vào C như:

int32_t divideBySeven(int32_t num) { 
    int32_t temp = ((int64_t)num * -015555555555) >> 32; 
    temp = (temp + num) >> 2; 
    return (temp - (num >> 31)); 
} 

Chúng ta hãy nhìn vào phần đầu tiên:

int32_t temp = ((int64_t)num * -015555555555) >> 32; 

Tại sao lại là số này?

Vâng, hãy lấy 2^64 và chia cho 7 và xem những gì bật ra.

2^64/7 = 2635249153387078802.28571428571428571429 

Trông giống như một mớ hỗn độn, điều gì sẽ xảy ra nếu chúng tôi chuyển đổi thành bát phân?

0222222222222222222222.22222222222222222222222 

Đó là một mẫu lặp lại rất đẹp, chắc chắn không thể trùng hợp được. Tôi có nghĩa là chúng ta nhớ rằng 7 là 0b111 và chúng ta biết rằng khi chúng ta chia cho 99 chúng ta có khuynh hướng lặp lại các mẫu trong cơ sở 10. Vì vậy, chúng ta sẽ có được một mẫu lặp lại trong cơ sở 8 khi chúng ta chia cho 7.

Vậy số điện thoại của chúng tôi xuất hiện ở đâu?

(int32_t)-1840700269 cũng giống như (uint_32t)2454267027

* 7 = 17179869189

Và cuối cùng là 17179869184 2^34

Có nghĩa là 17179869189 là nhiều gần gũi nhất của 7 2^34. Hoặc đặt nó theo cách khác 2454267027 là số lớn nhất sẽ phù hợp với một số uint32_t khi nhân với 7 là rất gần với một sức mạnh của 2

Số này là gì trong bát phân?

0222222222223 

Tại sao điều này quan trọng? Vâng, chúng tôi muốn chia cho 7. Con số này là 2^34/7 ... xấp xỉ. Vì vậy, nếu chúng ta nhân với nó, và sau đó leftshift 34 lần, chúng ta sẽ nhận được một số rất gần với con số chính xác.

Hai dòng cuối cùng trông giống như chúng được thiết kế để vá lỗi xấp xỉ.

Có lẽ ai đó có kiến ​​thức và/hoặc kiến ​​thức chuyên môn hơn trong lĩnh vực này có thể kêu gọi về điều này.

>>> magic = 2454267027 
>>> def div7(a): 
... if (int(magic * a >> 34) != a // 7): 
...  return 0 
... return 1 
... 
>>> for a in xrange(2**31, 2**32): 
... if (not div7(a)): 
...  print "%s fails" % a 
... 

thất bại bắt đầu vào 3435973841 là, hoạt kê đủ 0b11001100110011001100110011010001

Phân loại tại sao xấp xỉ thất bại là một chút ngoài tôi, và tại sao các bản vá lỗi sửa chữa nó lên là là tốt.Có ai biết làm thế nào ma thuật hoạt động vượt ra ngoài những gì tôi đã đặt xuống đây?

+0

http://www.hackersdelight.org/divcMore.pdf –

+0

pdf đó rất hữu ích trong việc xác định dòng cuối cùng là gì (ký hiệu sửa); tuy nhiên, nó dường như không thảo luận cụ thể thuật toán này, trừ khi tôi bỏ qua nó. – OmnipotentEntity

+1

Các tham chiếu chính xác là [ở đây] (http://gmplib.org/~tege/divcnst-pldi94.pdf) (được thực hiện trong trình biên dịch gcc) và theo dõi [ở đây] (http://gmplib.org/~tege/division-paper.pdf). Các triển khai có thể được tìm thấy trong thư viện [GMP] (http://gmplib.org/). ('udiv_qrnnd_preinv' trong' gmp-impl.h') –

Trả lời

9

Phần đầu tiên của thuật toán nhân với một xấp xỉ với nghịch đảo của 7. Trong trường hợp này, chúng tôi đang tính gần đúng tính toán nghịch đảo với phép nhân số nguyên và bit phải dịch.

Trước tiên, chúng tôi thấy giá trị -1840700269 (octal -015555555555) làm số nguyên 32 bit. Nếu bạn đọc nó dưới dạng số nguyên 32 bit không dấu, nó có giá trị 2454267027 (octal 22222222223). Nó chỉ ra rằng 2454267027/2^34 là một xấp xỉ số nguyên rất gần với 1/7.

Tại sao chúng tôi chọn số này và sức mạnh cụ thể này là 2? Các số nguyên chúng tôi sử dụng càng lớn thì xấp xỉ càng gần. Trong trường hợp này, 2454267027 có vẻ là số nguyên lớn nhất (thỏa mãn thuộc tính ở trên) mà bạn có thể nhân một int 32 bit đã ký mà không làm tràn int int 64 bit.

Tiếp theo, nếu chúng ta ngay lập tức chuyển ngay với >> 34 và lưu kết quả trong một int 32 bit, chúng tôi sẽ mất độ chính xác trong hai bit đặt hàng thấp nhất. Những bit đó là cần thiết để xác định tầng bên phải để phân chia số nguyên.

Tôi không chắc dòng thứ hai được dịch chính xác từ mã x86 hay không. Tại thời điểm đó, temp là khoảng num * 4/7, do đó, num * 4/7 + num cho điều đó và dịch chuyển bit sẽ cung cấp cho bạn khoảng num * 1/7 + num * 1/4, một lỗi khá lớn.

Ví dụ: lấy làm đầu vào 57, trong đó 57 // 7 = 8. Tôi xác nhận dưới đây trong mã cũng như:

  • 57 * 2454267027 = 139893220539
  • 139893220539 >> 32 = 32 (khoảng 57 * 4/7 = 32.5714... vào thời điểm này)
  • 32 + 57 = 89
  • 89 >> 2 = 22
  • (huh ?? Nowhere gần 8 vào thời điểm này.)

Dù sao, đối với dòng cuối cùng, đó là điều chỉnh chúng tôi thực hiện sau khi tính toán phân chia số nguyên đã ký theo cách này. Tôi xin trích dẫn từ phần từ niềm vui Hacker về bộ phận ký:

Mã này một cách tự nhiên nhất tính kết quả phân chia sàn, vì vậy chúng ta cần một sự điều chỉnh để làm cho nó tính toán thông thường cắt ngắn về phía 0 kết quả. Điều này có thể được thực hiện với ba hướng dẫn tính toán bằng cách thêm vào cổ tức nếu cổ tức là số âm.

Trong trường hợp này (đề cập đến bài đăng khác của bạn) có vẻ như bạn đang thực hiện ca đã ký, vì vậy, nó sẽ trừ -1 trong trường hợp số âm; đưa ra kết quả của +1.

Đây không phải là tất cả những gì bạn có thể làm; đây là một thậm chí điên rồ hơn blog post about how to divide by 7 with just a single multiplication.