Câu trả lời:
Theo phiên bản 2 của K & R , kết quả phụ thuộc vào việc thực hiện các thay đổi bên phải của các giá trị đã ký.
Wikipedia nói rằng C / C ++ 'thường' thực hiện một sự thay đổi số học trên các giá trị đã ký.
Về cơ bản, bạn cần kiểm tra trình biên dịch của mình hoặc không dựa vào nó. Trợ giúp VS2008 của tôi cho trình biên dịch MS C ++ hiện tại nói rằng trình biên dịch của họ thực hiện dịch chuyển số học.
Khi dịch chuyển sang trái, không có sự khác biệt giữa dịch chuyển số học và logic. Khi dịch chuyển sang phải, loại dịch chuyển phụ thuộc vào loại giá trị được dịch chuyển.
. . Sự khác biệt trở nên quan trọng khi xử lý các số âm.)
Khi dịch chuyển một giá trị không dấu, toán tử >> trong C là một sự thay đổi logic. Khi dịch chuyển một giá trị đã ký, toán tử >> là một sự thay đổi số học.
Ví dụ: giả sử máy 32 bit:
signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);
Xem xét i
và n
là các toán hạng trái và phải tương ứng của một toán tử thay đổi; loại i
, sau khuyến mãi số nguyên, được T
. Giả sử n
là trong [0, sizeof(i) * CHAR_BIT)
- không xác định khác - chúng tôi có những trường hợp sau:
| Direction | Type | Value (i) | Result |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | ≥ 0 | −∞ ← (i ÷ 2ⁿ) |
| Right | signed | < 0 | Implementation-defined† |
| Left (<<) | unsigned | ≥ 0 | (i * 2ⁿ) % (T_MAX + 1) |
| Left | signed | ≥ 0 | (i * 2ⁿ) ‡ |
| Left | signed | < 0 | Undefined |
Hầu hết các trình biên dịch thực hiện điều này dưới dạng dịch chuyển số học
không xác định nếu giá trị vượt quá loại kết quả T; loại quảng cáo của tôi
Đầu tiên là sự khác biệt giữa các thay đổi logic và số học từ quan điểm toán học, mà không phải lo lắng về kích thước loại dữ liệu. Các dịch chuyển logic luôn lấp đầy các bit bị loại bỏ bằng 0 trong khi dịch chuyển số học chỉ lấp đầy các số 0 cho dịch chuyển trái, nhưng đối với dịch chuyển phải, nó sao chép MSB do đó giữ nguyên dấu của toán hạng (giả sử mã hóa bổ sung hai cho các giá trị âm).
Nói cách khác, sự dịch chuyển logic xem toán hạng đã dịch chuyển như một luồng bit và di chuyển chúng, mà không bận tâm đến dấu hiệu của giá trị kết quả. Sự thay đổi số học xem nó như một số (đã ký) và giữ nguyên dấu hiệu khi các ca được thực hiện.
Sự dịch chuyển số học bên trái của một số X với n tương đương với nhân X với 2 n và do đó tương đương với dịch chuyển trái logic; một sự thay đổi hợp lý cũng sẽ cho kết quả tương tự vì dù sao MSB cũng rơi vào tình trạng cuối cùng và không có gì để bảo tồn.
Một sự dịch chuyển số học đúng của một số X theo n tương đương với phép chia số nguyên của X cho 2 n CHỈ nếu X không âm! Phân chia số nguyên không có gì ngoài phân chia toán học và làm tròn về 0 ( trunc ).
Đối với các số âm, được biểu thị bằng mã hóa bổ sung của hai, dịch chuyển sang phải bởi n bit có tác dụng chia toán học cho 2 n và làm tròn về phía ( sàn ); do đó dịch chuyển phải là khác nhau cho các giá trị không âm và âm.
cho X ≥ 0, X >> n = X / 2 n = trunc (X ÷ 2 n )
cho X <0, X >> n = sàn (X 2 n )
trong đó ÷
phân chia toán học, /
là phân chia số nguyên. Hãy xem xét một ví dụ:
37) 10 = 100101) 2
37 2 = 18,5
37/2 = 18 (làm tròn 18,5 về 0) = 10010) 2 [kết quả của sự dịch chuyển số học bên phải]
-37) 10 = 11011011) 2 (xem xét phần bù hai, biểu diễn 8 bit)
-37 2 = -18,5
-37 / 2 = -18 (làm tròn 18,5 về 0) = 11101110) 2 [KHÔNG phải là kết quả của sự dịch chuyển số học bên phải]
-37 >> 1 = -19 (làm tròn 18,5 về phía) = 11101101) 2 [kết quả của sự dịch chuyển số học bên phải]
Như Guy Steele đã chỉ ra , sự khác biệt này đã dẫn đến các lỗi trong nhiều trình biên dịch . Ở đây không âm (toán học) có thể được ánh xạ tới các giá trị không âm và không dấu (C); cả hai đều được xử lý như nhau và dịch chuyển sang phải được thực hiện bằng cách chia số nguyên.
Vì vậy, logic và số học là tương đương trong dịch chuyển trái và cho các giá trị không âm trong dịch chuyển phải; đó là sự dịch chuyển đúng của các giá trị âm mà chúng khác nhau.
Tiêu chuẩn C99 §6.5.7 :
Mỗi toán hạng sẽ có các kiểu số nguyên.
Các chương trình khuyến mãi số nguyên được thực hiện trên mỗi toán hạng. Loại kết quả là toán hạng bên trái được thăng cấp. Nếu giá trị của toán hạng bên phải là âm hoặc lớn hơn hoặc bằng chiều rộng của toán hạng bên trái được thăng cấp, hành vi không được xác định.
short E1 = 1, E2 = 3;
int R = E1 << E2;
Trong đoạn mã trên, cả hai toán hạng trở thành int
(do quảng cáo số nguyên); nếu E2
là âm hoặc E2 ≥ sizeof(int) * CHAR_BIT
sau đó hoạt động là không xác định. Điều này là do dịch chuyển nhiều hơn các bit có sẵn chắc chắn sẽ tràn. Đã R
được tuyên bố là short
, int
kết quả của hoạt động thay đổi sẽ được chuyển đổi hoàn toàn thành short
; một chuyển đổi thu hẹp, có thể dẫn đến hành vi được xác định thực hiện nếu giá trị không thể biểu thị trong loại đích.
Kết quả của E1 << E2 là vị trí bit E2 dịch chuyển trái; bit trống được điền với số không. Nếu E1 có loại không dấu, giá trị của kết quả là E1 × 2 E2 , giảm modulo một lần so với giá trị tối đa có thể biểu thị trong loại kết quả. Nếu E1 có loại đã ký và giá trị không âm và E1 × 2 E2 có thể biểu thị trong loại kết quả, thì đó là giá trị kết quả; mặt khác, hành vi là không xác định.
Vì các dịch chuyển trái là giống nhau cho cả hai, các bit bị bỏ trống chỉ đơn giản là chứa các số không. Sau đó, tuyên bố rằng đối với cả hai loại không dấu và có chữ ký, đó là một sự thay đổi số học. Tôi đang giải thích nó như là sự dịch chuyển số học vì các dịch chuyển logic không bận tâm về giá trị được biểu thị bởi các bit, nó chỉ xem nó như một luồng bit; nhưng tiêu chuẩn nói không phải về các bit, mà bằng cách định nghĩa nó theo giá trị thu được từ sản phẩm của E1 với 2 E2 .
Thông báo trước ở đây là đối với các loại đã ký, giá trị phải không âm và giá trị kết quả phải được biểu thị trong loại kết quả. Nếu không, hoạt động là không xác định. Loại kết quả sẽ là loại của E1 sau khi áp dụng khuyến mãi tích phân và không phải là đích (biến sẽ giữ kết quả). Giá trị kết quả được chuyển đổi hoàn toàn sang loại đích; nếu nó không thể biểu diễn trong loại đó, thì chuyển đổi được xác định theo triển khai (C99 §6.3.1.3 / 3).
Nếu E1 là loại đã ký có giá trị âm thì hành vi dịch chuyển trái không được xác định. Đây là một lộ trình dễ dàng đến hành vi không xác định có thể dễ dàng bị bỏ qua.
Kết quả của E1 >> E2 là vị trí bit E2 dịch chuyển sang phải. Nếu E1 có loại không dấu hoặc nếu E1 có loại đã ký và giá trị không âm, giá trị của kết quả là phần không thể thiếu của thương số của E1 / 2 E2 . Nếu E1 có loại đã ký và giá trị âm, giá trị kết quả được xác định theo thực hiện.
Sự thay đổi bên phải đối với các giá trị không âm và không dấu được ký là khá đơn giản; các bit còn trống chứa đầy số không. Đối với các giá trị âm đã ký, kết quả của dịch chuyển phải được xác định theo thực hiện. Điều đó nói rằng, hầu hết các triển khai như GCC và Visual C ++ đều thực hiện dịch chuyển sang phải dưới dạng dịch chuyển số học bằng cách bảo toàn bit dấu.
Không giống như Java, có một toán tử đặc biệt >>>
để dịch chuyển logic ngoài thông thường >>
và <<
, C và C ++ chỉ có dịch chuyển số học với một số khu vực không được xác định và xác định thực hiện. Lý do tôi coi chúng là số học là do cách diễn đạt chuẩn của phép toán hơn là coi toán hạng dịch chuyển như một luồng bit; đây có lẽ là lý do tại sao nó khiến các khu vực đó không được xác định / triển khai thay vì chỉ xác định tất cả các trường hợp là thay đổi logic.
-Inf
cho cả số âm và số dương. Làm tròn về 0 của một số dương là một trường hợp riêng của làm tròn theo hướng -Inf
. Khi cắt ngắn, bạn luôn giảm các giá trị có trọng số dương, do đó bạn trừ đi kết quả chính xác khác.
Về loại dịch chuyển bạn nhận được, điều quan trọng là loại giá trị mà bạn đang thay đổi. Một nguồn lỗi cổ điển là khi bạn chuyển một nghĩa đen sang, nói, che dấu các bit. Ví dụ: nếu bạn muốn bỏ phần lớn nhất bên trái của một số nguyên không dấu, thì bạn có thể thử điều này làm mặt nạ của mình:
~0 >> 1
Thật không may, điều này sẽ khiến bạn gặp rắc rối vì mặt nạ sẽ có tất cả các bit của nó được đặt vì giá trị được dịch chuyển (~ 0) được ký, do đó, một sự thay đổi số học được thực hiện. Thay vào đó, bạn muốn buộc một sự thay đổi hợp lý bằng cách tuyên bố rõ ràng giá trị là không dấu, tức là bằng cách làm như thế này:
~0U >> 1;
Dưới đây là các hàm để đảm bảo dịch chuyển phải logic và dịch chuyển số học phải của một số nguyên trong C:
int logicalRightShift(int x, int n) {
return (unsigned)x >> n;
}
int arithmeticRightShift(int x, int n) {
if (x < 0 && n > 0)
return x >> n | ~(~0U >> n);
else
return x >> n;
}
Khi bạn làm - dịch chuyển trái sang 1, bạn nhân với 2 - dịch chuyển phải cho 1 bạn chia cho 2
x = 5
x >> 1
x = 2 ( x=5/2)
x = 5
x << 1
x = 10 (x=5*2)
Chà, tôi đã tra cứu nó trên wikipedia và họ có điều này để nói:
C, tuy nhiên, chỉ có một toán tử dịch chuyển bên phải, >>. Nhiều trình biên dịch C chọn dịch chuyển đúng nào để thực hiện tùy thuộc vào loại số nguyên nào đang được dịch chuyển; các số nguyên được ký thường được dịch chuyển bằng cách sử dụng dịch chuyển số học và các số nguyên không dấu được dịch chuyển bằng cách sử dụng dịch chuyển logic.
Vì vậy, có vẻ như nó phụ thuộc vào trình biên dịch của bạn. Cũng trong bài viết đó, lưu ý rằng dịch chuyển trái là giống nhau cho số học và logic. Tôi sẽ khuyên bạn nên thực hiện một thử nghiệm đơn giản với một số số đã ký và không dấu trên trường hợp viền (tất nhiên là tập bit cao) và xem kết quả trên trình biên dịch của bạn là gì. Tôi cũng khuyên bạn nên tránh phụ thuộc vào nó là cái này hay cái kia vì dường như C không có tiêu chuẩn, ít nhất là nếu nó hợp lý và có thể để tránh sự phụ thuộc như vậy.
Dịch trái <<
Điều này bằng cách nào đó dễ dàng và bất cứ khi nào bạn sử dụng toán tử thay đổi, nó luôn luôn là một hoạt động khôn ngoan, vì vậy chúng tôi không thể sử dụng nó với một hoạt động kép và nổi. Bất cứ khi nào chúng tôi rời ca một số 0, nó luôn được thêm vào bit có ý nghĩa nhỏ nhất ( LSB
).
Nhưng trong ca làm việc đúng, >>
chúng ta phải tuân theo một quy tắc bổ sung và quy tắc đó được gọi là "sao chép bit dấu". Ý nghĩa của "sao chép bit dấu" là nếu bit quan trọng nhất ( MSB
) được đặt thì sau khi dịch chuyển phải một lần nữa, MSB
nó sẽ được đặt nếu được đặt lại sau đó được đặt lại, nghĩa là nếu giá trị trước đó bằng 0 thì sau khi dịch lại lần nữa, bit bằng 0 nếu bit trước đó là một thì sau khi dịch chuyển nó lại là một. Quy tắc này không áp dụng cho một ca trái.
Ví dụ quan trọng nhất về dịch chuyển phải nếu bạn dịch chuyển bất kỳ số âm nào sang dịch chuyển sang phải, sau đó sau một số thay đổi, giá trị cuối cùng sẽ về 0 và sau đó nếu thay đổi này -1 bất kỳ số lần nào giá trị sẽ giữ nguyên. Hãy kiểm tra.
GCC nào
cho -ve -> Dịch chuyển số học
Dành cho + ve -> Dịch chuyển logic
Theo nhiều người c trình biên dịch:
<<
là một sự thay đổi số học hoặc thay đổi trái bit bit.>>
là một sự thay đổi bên phải số học bit shift phải.>>
số học hay bitwise (logic)?" Bạn đã trả lời " >>
là số học hoặc bitwise." Điều đó không trả lời câu hỏi.
<<
và các >>
toán tử là logic, không phải là số học