6

cách nhanh nhất để thực hiệnCách nhanh nhất để có được chữ số thập phân cao nhất của một số nguyên là gì?

template <typename T> 
unsigned highest_decimal_digit(T x); 

(trả về ví dụ 3 cho 356.431, 7 cho 71 và 9 trong 9) là gì?

Điều tốt nhất tôi có thể nghĩ đến là:

  • constexpr-tính "kích thước trung" sức mạnh của 10 mà phù hợp với T.
  • thực hiện tìm kiếm nhị phân (trong quyền hạn của 10, có thể sử dụng bảng tra cứu được cấu thành bằng constexpr) để tìm p, công suất cao nhất trong số 10 thấp hơn x.
  • trả lại x chia cho p

... nhưng có thể có cách tiếp cận khác.

Ghi chú:

  • tôi bày tỏ câu hỏi và cách tiếp cận của tôi trong C++ 14ish điều khoản, và một giải pháp trong mã sẽ được tốt đẹp, nhưng một giải pháp trừu tượng (hoặc thậm chí là một giải pháp trong lắp ráp x86_64) sẽ ổn thôi. Tôi muốn một cái gì đó mà sẽ làm việc cho tất cả các loại số nguyên (unsigned), mặc dù.
  • Bạn có thể bỏ qua các loại tích phân đã ký.
  • Tôi không chỉ định "nhanh" là gì, nhưng vui lòng nhận thức về phần cứng.
+0

Có sử dụng chuỗi không được phép không? ..... – yobro97

+0

@manlio thực sự, và thậm chí câu trả lời hay nhất có phù hợp với tôi: P – Vesper

+0

@ yobro97: Không có cách nào mà bất kỳ công việc nào có chuỗi cho phép giải pháp nhanh. – einpoklum

Trả lời

0

Tùy chọn xảy ra với tôi;

Lực lượng vũ phu: giữ nguyên chia cho 10 cho đến khi bạn đạt được 0; cho bạn biết thứ tự của số bạn đang xem (ví dụ 860 có 3 ca (86, 8, 0) nên đó là 10^3) sau đó trả lại n/(10^order)

Tìm kiếm nhị phân: như bạn nói, tìm kiếm trên quyền hạn của 10, nhưng nó đòi hỏi các biến bổ sung và các bài tập và mối quan tâm sẽ là, liệu có thêm thông tin theo dõi trả tiền cho chính nó trên các loại số bạn quan tâm? Ví dụ, nếu hầu hết các số của bạn là nhỏ, lực lượng vũ phu chỉ có thể nhanh hơn.

Tối ưu hóa bithift: đếm số lần bạn cần làm x >> 1 cho đến khi bạn đạt đến 0; điều này đặt phạm vi cho tìm kiếm của bạn. Ví dụ: 94 mất 7 ca để xóa số. Vì vậy, nó là < 128. Vì vậy, bắt đầu tìm kiếm brute-force tại 10^3. Bạn sẽ cần tra cứu bit => order.

+0

Phân chia là loại đắt tiền ... Tôi nghĩ rằng ít nhất đối với các số 32 bit và 64 bit, nó đáng để làm điều gì đó thông minh hơn. Đối với "đếm ca" - không có nhu cầu cho điều đó, chúng tôi có CLZ trong phần cứng những ngày này. – einpoklum

+0

Tôi đã không thực sự có đủ thông minh lắp ráp để trợ giúp. Trừ khi cung cấp rác, bạn có thể cứu vớt;) –

+1

Điều đó nói rằng, nếu bạn có thể làm 'clz', sau đó chuyển số đó thành lũy thừa tối đa là mười, bạn chỉ phải thử một số lượng nhỏ các thử nghiệm, tôi nghĩ vậy. Tôi không biết đủ để giúp đỡ nhiều hơn nữa. –

1

Chip x86 mới hơn hỗ trợ lệnh lzcnt cho bạn biết số bit rõ ràng khi bắt đầu số nguyên. Bạn có thể truy cập nó bằng cách sử chức năng biên dịch tích hợp như sau (từ GCC):

unsigned short __builtin_ia32_lzcnt_16(unsigned short); 
unsigned int __builtin_ia32_lzcnt_u32(unsigned int); 
unsigned long long __builtin_ia32_lzcnt_u64 (unsigned long long); 

Bạn có thể kết hợp điều này với một bảng tra cứu 640 giá trị chỉ ra các giới hạn thấp hơn và trên các số nguyên bắt đầu với mỗi chữ số từ 0-9 bắt đầu với số bit rõ ràng tương ứng. Trong thực tế, bạn có thể tiết kiệm không gian bằng cách dịch chuyển đúng giá trị lzcnt 3 vị trí; sự tương ứng với các chữ số thập phân đầu tiên sẽ vẫn là duy nhất.

1

Với lệnh lzcnt, bạn có thể tạo bảng chia cho mỗi số bit không có hàng đầu. Ví dụ, đối với số lượng 64 bit unsigned:

lz | range | div 
---+---------+---- 
64 | 0  | 1 
63 | 1  | 1 
62 | 2-3 | 1 
61 | 4-7 | 1 
60 | 8-15 | 1 
59 | 16-31 | 10 
58 | 32-63 | 10 
57 | 64-127 | 10 
56 | 128-255 | 100 
... 
0 | 9223372036854775808-18446744073709551615 | 1000000000000000000 

Sau đó, việc tính toán trở thành:

leading_zero_bits = lzcnt(x); 
leading_digit = x/divisor_table[leading_zero_bits]; 
if (leading_digit >= 10) leading_digit = 1; 

Kết quả của việc phân chia sẽ luôn là dưới 20, vì vậy chỉ một kiểm tra đơn giản là cần thiết cho thương số giữa 10 và 19. Phép chia theo hằng số cũng có thể được tối ưu hóa.

+0

Bạn sẽ phải chắc chắn rằng bảng trong bộ nhớ cache L1 của bạn cho điều này để được nhanh chóng, mặc dù. – einpoklum

2

Các lựa chọn tốt nhất có vẻ là kết hợp cách tiếp cận CLZ và chia cho điện precalculated 10. Vì vậy, trong giả:

powers10=[1,1,1,1,10,10,10,10,100,100...]; // contains powers of 10 map to CLZ values 
int firstDigit(unsigned INT_TYPE a) { 
    if (a<10) return a; // one digit, screw it 
    int j=typesize(a)-clz(a); 
    if (j==3) return 1; // 10-16 hit this, return 1 
    int k=a/powers10[j]; 
    if (k>9) return 1; else return k; 
} 

typesize() lợi nhuận 64 cho long long, 32 cho int và 16 cho short int.

+0

Điều đó sẽ rất chậm, vì nó đòi hỏi phải đọc từ RAM. Nếu bạn chạy nó nhiều lần nó sẽ có phần tốt hơn (L1 hy vọng), nhưng tôi vẫn còn nghi ngờ rất nhiều đó là cách nhanh nhất để làm điều đó. – einpoklum

+0

@einpoklum Vâng, phần cứng-khôn ngoan, nó rẻ hơn để đưa toàn bộ bảng vào L1 hơn thực hiện tính toán sức mạnh của mười đó là cần thiết để phân chia, và chia luôn luôn tốn kém hơn nhân, đặc biệt là bởi hằng số, vì vậy chia cho 10 liên tục sẽ đắt hơn chia một lần với một truy cập RAM. Ngoài ra, 'powers10' có thể được đặt trên stack, vì nó khá nhỏ, chỉ 64x8 = 512 byte, và stack có khả năng nằm trên L1 ở bất cứ hệ thống nào có cache L1. – Vesper

+0

Có các lựa chọn thay thế không có phép chia lặp lại 10 mà không yêu cầu đọc bất kỳ thứ gì từ RAM - ví dụ: lũy thừa 10, chỉ sử dụng phép nhân, rẻ tiền. Đối với L1 - bạn có thể giữ bàn của bạn ở đó, hoặc bạn có thể không. – einpoklum

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