به C# 11، عملگر جدیدی به شکل <<< و به معنای unsigned right shift اضافه شدهاست که ... در زبان جاوا از نگارش ابتدایی آن حضور داشتهاست. اما ... چرا از این لحاظ بین این دو زبان، تفاوت وجود داشتهاست؟
مفهوم عملگر شیفت در #C
عملگر شیفت به سمت راست و یا <<، عددی را به تعداد بیت مشخص شده (x >> count)، به سمت راست منتقل میکند و دو نوع دارد:
الف) شیفت به راست منطقی
برای مثال اگر عدد 12 را به صورت باینری نمایش دهیم، به صورت زیر خواهد بود:
و اگر آنرا به اندازهی یک بیت به سمت راست هدایت کنیم، که با 1 <<< 12 نمایش داده میشود:
به عدد 6 خواهیم رسید.
در این حالت همواره فرض میشود که عدد مدنظر، unsigned است.
ب) شیفت به راست ریاضی
شیفت به راست ریاضی، دقیقا مانند شیفت به راست منطقی است؛ مانند مثال زیر که عدد 1001 باینری را دو بیت به سمت راست منتقل میکند:
اما ... بجای اینکه همانند شیفت به راست منطقی، سمت چپ را با صفر پر کند، آنرا با «با ارزشترین بیت یا همان بیت علامت» پر میکند. یعنی در اینجا بیتی که بیانگر مثبت و منفی بودن عدد است، حفظ میشود. یعنی این نوع شیفت، با اعداد signed هم کار میکند.
برای مثال نمایش باینری عدد منفی 2,147,483,552- به صورت زیر است:
و اگر آنرا چهار بیت به سمت راست هدایت کنیم (یعنی 4 << 2,147,483,552 -)، به عدد 134,217,722- میرسیم که معادل عدد باینری زیر است:
به این ترتیب با شیفت به راست ریاضی، علامت عدد منفی حفظ شدهاست. مثالی دیگر:
سؤال: در زبان جاوا، عملگر <<< به معنای unsigned right shift است؛ اما چنین عملگری در زبان #C تا نگارش 11 آن وجود ندارد؛ چرا؟!
تا پیش از C# 11 اگر نیاز به کار بر روی signed types جهت رسیدن به نتیجهی عملگر <<< وجود داشته باشد (انجام شیفت منطقی؛ یعنی صرفنظر کردن از نوع علامت عدد)، میتوان از متدهای الحاقی زیر استفاده کرد که ابتدا آنها را به نمونههای unsigned تبدیل میکند و کار شیفت را انجام میدهد و سپس نوع اصلی را بازیابی میکند:
مفهوم عملگر شیفت در #C
عملگر شیفت به سمت راست و یا <<، عددی را به تعداد بیت مشخص شده (x >> count)، به سمت راست منتقل میکند و دو نوع دارد:
الف) شیفت به راست منطقی
برای مثال اگر عدد 12 را به صورت باینری نمایش دهیم، به صورت زیر خواهد بود:
00000000 00000000 00000000 00001100
00000000 00000000 00000000 00000110
در این حالت همواره فرض میشود که عدد مدنظر، unsigned است.
ب) شیفت به راست ریاضی
شیفت به راست ریاضی، دقیقا مانند شیفت به راست منطقی است؛ مانند مثال زیر که عدد 1001 باینری را دو بیت به سمت راست منتقل میکند:
uint e = 0b_1001; Console.WriteLine($"Before: {Convert.ToString(e, toBase: 2),4}"); // Before: 1001 uint f = e >> 2; Console.WriteLine($"After: {Convert.ToString(f, toBase: 2).PadLeft(4, '0'),4}"); // After: 0010
برای مثال نمایش باینری عدد منفی 2,147,483,552- به صورت زیر است:
10000000 00000000 00000000 01100000
11111000 00000000 00000000 00000110
int x = -8; Console.WriteLine($"Before: {x,11}, hex: {x,8:x}, binary: {Convert.ToString(x, toBase: 2),32}"); // Before: -8, hex: fffffff8, binary: 11111111111111111111111111111000 int y = x >> 2; Console.WriteLine($"After >>: {y,11}, hex: {y,8:x}, binary: {Convert.ToString(y, toBase: 2),32}"); // After >>: -2, hex: fffffffe, binary: 11111111111111111111111111111110 int z = x >>> 2; Console.WriteLine($"After >>>: {z,11}, hex: {z,8:x}, binary: {Convert.ToString(z, toBase: 2).PadLeft(32, '0'),32}"); // After >>>: 1073741822, hex: 3ffffffe, binary: 00111111111111111111111111111110
سؤال: در زبان جاوا، عملگر <<< به معنای unsigned right shift است؛ اما چنین عملگری در زبان #C تا نگارش 11 آن وجود ندارد؛ چرا؟!
پاسخ: چون زبان جاوا، فاقد نوعهای دادهای توکار unsigned integers است (^)؛ برخلاف #C از نگارش یک آن. حتی به ظاهر، این امکان در Java 8 اضافه شدهاست، اما در حقیقت با نوعهای int و long، فقط مانند اینکه unsigned هم میتوانند باشند، رفتار میکند (^). البته نوع char در زبان Java، تنها نوع unsigned واقعی است.
همانطور که عنوان شد، در زبان #C فقط کافی است بر روی unsigned types مانند ulong, uint, ushort، عملگر << را بکار برد تا به unsigned right shift جاوا رسید (به همین جهت عملگر اضافهتری برای آن ارائه نشده بود). البته باید دقت داشت که در اینجا عملگر << کار پر کردن MSB یا «با ارزشترین بیت یا همان بیت علامت» را هم با صفر انجام میدهد؛ حتی اگر MSB مقدار دهی شده باشد (چون این کاری است که << بر روی unsigned types انجام میدهد).تا پیش از C# 11 اگر نیاز به کار بر روی signed types جهت رسیدن به نتیجهی عملگر <<< وجود داشته باشد (انجام شیفت منطقی؛ یعنی صرفنظر کردن از نوع علامت عدد)، میتوان از متدهای الحاقی زیر استفاده کرد که ابتدا آنها را به نمونههای unsigned تبدیل میکند و کار شیفت را انجام میدهد و سپس نوع اصلی را بازیابی میکند:
public static int UnsignedRightShift(this int signed, int places) { unchecked { var unsigned = (uint)signed; unsigned >>= places; return (int)unsigned; } } public static long UnsignedRightShift(this long signed, int places) { unchecked { var unsigned = (ulong)signed; unsigned >>= places; return (long)unsigned; } }