Показаны сообщения с ярлыком long. Показать все сообщения
Показаны сообщения с ярлыком long. Показать все сообщения

23 апр. 2015 г.

Преобразование примитивных типов в Java - Практика

Теперь немного практики чтобы закрепить предыдущую тему. Сперва простая программа неявного преорбазования примитивных типов Java.

С0004Тут все предельно просто. Единственное что стоит отметить, что я задал значение переменной i типа int в двоичной системе счисления, чтобы было явно видно, что количество значимых бит больше чем 23.
Тут же приведу и вывод этой программы:
С0005

Как  можно заметить, есть небольшая потеря точности при преобразовании целочисленного значения int к значению типа float.

Так же обратите внимание что значение переменной типа char спокойно преобразовалось в другие, более емкие, типы.

Теперь перейдем к примерам явного преобразования.

С0006[4][2]Тут все тоже достаточно просто. Единственное что может надо пояснить почему число 454.874 было преобразовано в –58. Казалось бы удивительный результат и почти магический, но магии тут ни какой нет, тут одна матчасть.

Достаточно просто посмотреть на двоичное представление числа 454 и отбросить все старшие биты, оставив только младшие восемь.
В результате в восьми младших битах получаем число 11000110, которое для типа int соответствует числу 198, а для типа byte числу –58, поскольку первый бит для него знаковый, поэтому данное число является отрицательным.

Если чуть более глубоко капнуть, то тоже самое получается если разделить число 454 по модулю на диапазон целевого типа, который в данном случае byte и его диапазон покрывает 256 значений.

454 % 256 = 198

Число 198 можно представить восемью битами, про это мы уже говорили выше. И то что для int 198, то для byte –58. Вот такая арифметика.

Далее у нас идет пример когда значение float слишком большое для значения int, в данном случае значение int получает свое максимальное или минимальное значение (с учетом знака).

Затем пример с литералом типа long который преобразуется в тип int. Но в данном случае это уже целочисленный литерал который больше допустимого значения для int. Тут опять действует правило отбрасывания старших битов которые не помещаются  в int.


Ну и затем совсем простой пример с классом Byte, который является оберткой для типа byte.

Теперь посмотрим на вывод этой программы:

21 апр. 2015 г.

Преобразование примитивных типов в Java

Преобразование типов в Java это достаточно большая тема, но мы постараемся рассмотреть ее как можно более полно и вместе с тем компактно. Частично мы уже касались этой темы когда рассматривали примитивные типы Java.

В Java возможны преобразования между целыми значениями и значениями с плавающей точкой. Кроме того, можно преобразовывать значения целых типов и типов с плавающей точкой в значения типа char и наоборот, поскольку каждый символ соответствует цифре в кодировке Unicode. Фактически тип boolean является единственным примитивным типом в Java, который нельзя преобразовать в другой примитивный тип. Кроме того, любой другой примитивный тип нельзя преобразовать в boolean.

Преобразование типов в Java бывает двух видов: неявное и явное.

Неявное преобразование типов выполняется в случае если выполняются условия:

  1. Оба типа совместимы
  2. Длина целевого типа больше или равна длине исходного типа

JavaBasics_ImplicitTypeCastingPrimitives

Во всех остальных случаях должно использоваться явное преобразование типов.

Так же существуют два типа преобразований:

  1. Расширяющее преобразование (widening conversion)
  2. Сужающее преобразование (narrowing conversion)

Расширяющее преобразование (widening conversion) происходит, если значение одного типа преобразовывается в более широкий тип, с большим диапазоном допустимых значений. Java выполняет расширяющие преобразования автоматически, например, если вы присвоили литерал типа int переменной типа double или значение пепременной типа char переменной типа int. Неявное преобразование всегда имеет расширяющий тип.

GrabliНо у тут могут быть свои небольшие грабельки. Например если преобразуется значение int в значение типа float. И у значения int в двоичном представлении больше чем 23 значащих бита, то возможна потеря точности, так как у типа float под целую часть отведено 23 бита. Все младшие биты значения int, которые не поместятся в 23 бита мантиссы float, будут отброшены, поэтому хотя порядок числа сохраниться, но точность будет утеряна. То же самое справедливо для преобразования типа long в тип double.

Расширяющее преобразование типов Java можно изобразить еще так:

JavaBasics_ImplicitTypeCastingPrimitivesDetailed

Сплошные линии обозначают преобразования, выполняемые без потери данных. Штриховые линии говорят о том, что при преобразовании может произойти потеря точности.

Стоит немного пояснить почему, к примеру тип byte не преобразуется автоматически (не явно) в тип char, хотя тип byte имеет ширину 8 бит, а char 16, тоже самое касается и преобразования типа short в char. Это происходит потому, что byte и short знаковые типы данных, а char без знаковый. Поэтому в данном случае требуется использовать явное приведение типов, поскольку компилятору надо явно указать что вы знаете чего хотите и как будет обрабатываться знаковый бит типов byte и short при преобразовании к типу char.

Поведение величины типа char в большинстве случаев совпадает с поведением величины целого типа, следовательно, значение типа char можно использовать везде, где требуются значения int или long. Однако напомним, что тип char не имеет знака, поэтому он ведет себя отлично от типа short, несмотря на то что диапазон обоих типов равен 16 бит.

short s = ( short) 0xffff; // Данные биты представляют число –1
char c = '\uffff'; // Те же биты представляют символ юникода
int i1 = s; // Преобразование типа short в int дает –1
int i2 = c; // Преобразование char в int дает 65535

Сужающее преобразование (narrowing conversion) происходит, если значение преобразуется в значение типа, диапазон которого не шире изначального. Сужающие преобразования не всегда безопасны: например, преобразование целого значения 13 в byte имеет смысл, а преобразование 13000 в byte неразумно, поскольку byte может хранить только числа от −128 до 127. Поскольку во время сужающего преобразования могут быть потеряны данные, Java компилятор возражает против любого такого преобразования, даже если преобразуемое значение укладывается в более узкий диапазон указанного типа:

int i = 13;
byte b = i; // Компилятор не разрешит это выражение

Единственное исключение из правила – присвоение целого литерала (значения типа int) переменной byte или short, если литерал соответствует диапазону переменной.

Сужающее преобразование это всегда явное преобразование типов.

Явное преобразование примитивных типов

Оператором явного преобразования типов или точнее говоря приведения типов являются круглые скобки, внутри которых указан тип, к которому происходит преобразование – (type). Например:

int i = 13;
byte b = (byte) i; // Принудительное преобразование int в byte
i = (int) 13.456; // Принудительное преобразование литерала типа double в int 13

Приведение примитивных типов чаще всего используют для преобразования значений с плавающей точкой в целые числа. При этом дробная часть значения с плавающей точкой просто отбрасывается (то есть значение с плавающей точкой округляется по направлению к нулю, а не к ближайшему целому числу). По существу берется только целочисленная часть вещественного типа и она уже приводится к целевому типу целочисленного числа.

При приведении более емкого целого типа к менее емкому старшие биты просто отбрасываются. По существу это равнозначно операции деления по модулю приводимого значения на диапазон целевого типа (например для типа byte это 256).

Слишком большое дробное число при приведении к целому превращается в MAX_VALUE или MIN_VALUE.

Слишком большой double при приведении к float превращается в Float.POSITIVE_INFINITY или Float.NEGATIVE_INFINITY.

Таблица представленная ниже представляет собой сетку, где для каждого примитивного типа указаны типы, в которые их можно преобразовать, и способ преобразования. Буква N в таблице означает невозможность преобразования. Буква Y означает расширяющее преобразование, которое выполняется автоматически. Буква С означает сужающее преобразование, требующее явного приведения. Наконец, Y* означает автоматическое расширяющее преобразование, в процессе которого значение может потерять некоторые из наименее значимых разрядов. Это может произойти при преобразовании int или long во float или double. Типы с плавающей точкой имеют больший диапазон, чем целые типы, поэтому int или long можно представить посредством float или double. Однако типы с плавающей точкой являются приближенными числами и не всегда могут содержать так много значащих разрядов в мантиссе, как целые типы.

JavaBasics_ImplicitTypeCastingPrimitivesDetailedTable

Автоматическое расширение типов в выражениях

Так же стоит еще раз упомянуть об автоматическом повышении (расширении) типов в выражениях. Мы с этим уже сталкивались когда рассматривали целочисленные типы данных и операции над ними, но все же стоит и тут напомнить, чтобы усвоилось еще лучше и к тому же это имеет непосредственное отношение к данной теме. В примере ниже знак @ означает любой допустимый оператор, например +, , *, / и т.п.

С0001

То есть, все целочисленные литералы в выражениях, а так же типы byte, short и char расширяются до int. Если, как описано выше, в выражении не присутствуют другие, более большие типы данных (long, float или double). Поэтому приведенный выше пример вызовет ошибку компиляции, так как переменная c имеет тип byte, а выражение b+1, в результате автоматического повышения имеет тип int.

Неявное приведение типов в выражениях совмещенного присваивания

Хоть данный раздел и относится к неявному преобразованию (приведению) типов, его объяснение мы привели тут, поскольку в данном случае так же работает и автоматическое расширение типов в выражениях, а затем уже неявное приведение типов. Вот такой кордебалет. Пример ниже я думаю все разъяснит. Так же как и в предыдущем объяснении знак @ означает любой допустимый оператор, например +, , *, / и т.п.

С0002

Это стоит пояснить на простом примере:

byte b2 = 50;
b2 = b2 * 2; // не скомпилируется
b2 *= 2; //скомпилируется, хотя и равнозначна b2 = b2 * 2

Вторя строка, приведенная в примере не скомпилируется из-за автоматического расширения типов в выражениях, так как выражение b2*2 имеет тип int, так как происходит автоматическое расширение типа (целочисленные литералы в выражении всегда int). Третья же строка спокойно скомпилируется, так как в ней сработает неявное приведение типов в совмещенном выражении присваивания.

Boxing/unboxing – преобразование примитивных типов в объекты обертки

Boxing и unboxin – это тоже достаточно большая тема, но она достаточно простая.

По существу boxing и unboxing это преобразование примитивных типов в объекты обертки и обратно.

Для объектов оберток примитивных типов применимо все что было сказано выше.

Об классах обертках упоминалось в таблицах, при разборе каждого из примитивных типов. Но тогда это было лишь упоминание в таблице.

Так вот, для каждого примитивного типа есть его старший брат, и он совсем не примитивный, а является настоящим классом, с полями и методами. И для каждой такой парочки возможно автоматическое преобразование.

С0003

Обычно, если в программе есть много математических вычислений, то лучше пользоваться примитивными типами, так как это быстрее и экономнее с точки зрения ресурсов, но иногда бывает необходимость преобразовать примитивный тип в объект.

Приведу простой пример:

int i3;
byte b2=3;
Byte myB;
myB=b2;
myB++;
b2=myB;
i3=myB;

Если пока не понятно зачем это нужно, то это не страшно, просто завяжите узелок на память.

13 апр. 2015 г.

Примитивные целочисленные типы Java - byte, short, int, long, char

Особенности типа char мы рассмотрели в предыдущей статье. Здесь же мы рассмотрим все целочисленные примитивные типы Java оптом, так как они отличаются друг от друга только размером, а следовательно, только максимальным и минимальным значениями, которые могут содержать. Все типы за исключением char являются знаковыми, char, как уже объяснялось без знаковый. Повторим таблицу размеров и значений для этих типов тут еще раз:

  Тип   Содержит   По умолчанию   Размер   Диапазон   Обертки
  char   целое беззнаковое   \u0000   16 bits   от \u0000  до \uFFFF (от 0 до 65535 десятичное)   Charaster
  byte   целое знаковое   0   8 bits   от -128 до 127   Byte
  short   целое знаковое   0   16 bits   от -32768 до 32767   Short
  int   целое знаковое   0   32 bits   от -2147483648 до 2147483647   Integer
  long   целое знаковое   0   64 bits   от -9223372036854775808 до 9223372036854775807   Long

Целочисленные литералы

Значения целочисленным переменным и константам задаются при помощи следующих литералов:

108 – десятичный литерал
0154 – восьмеричный литерал (в начале стоит ноль)
0x6c – шестнадцатеричный литерал
0b01101100 – двоичный литерал (с Java 7)

Так же в числовых литералах можно использовать символ подчеркивания _ для разграничения разрядов, это сделано только для удобства написания и понимания и ни как не влияет на значение (с Java 7). Например:

0110_1100, 0x7fff_ffff, 0177_7777_7777

Для обозначения литерала типа long можно использовать суффикс L (можно использовать и маленькую l, но ее легко попутать с единицей 1). Например:

3_000_000_000L

В связи с этим есть один интересный момент, что переменной типа long не возможно задать значения больше 2147483647 следующим оператором присваивания:

long iLong = 2147483648;

Хотя значение 2147483648 является допустимым для типа long, но компилятор считает это число 2147483648 литералом типа int, поскольку у него нет суффикса L, а для типа int число 2147483648 является не допустимым значением и поэтому компилятор выдаст ошибку. Чтобы этого не было надо использовать суффикс L. Например:

long iLong = 2147483648L;

Такое выражение компилятор уже примет как правильное.

Над целочисленными типами можно совершать различные операции при помощи операторов сравнения, математических операторов и операторов побитового сдвига.

Операции сравнения для примитивных типов вообще и для целочисленных в частности сравнивают их значения. Операторы сравнения мы уже рассматривали при изучении типа boolean. Результатом этих операций может быть только значение типа boolean.

Арифметические операции

I00001

Тут впрочем все просто. На что только надо обратить внимание это то, что деление над целочисленными типами тоже целочисленное и в результате этих операций можно получить как целое число или целую или дробную часть от операции совершенной соответствующими операторами. Инкремент и декремент (увеличение или уменьшение на единицу) может быть как префиксным, так и постфиксным. При использовании префиксного оператора сперва происходит инкремент иди декремент переменной,  а затем используется ее значение, при постфиксной наоборот – сперва используется текущее значение переменной, а затем происходит ее декремент или инкремент.

 

Grabli

Результат арифметической операции имеет тип int, кроме того случая, когда один из
операндов типа long. В этом случае результат будет типа long
. Перед выполнением арифметической операции всегда происходит повышение (promotion) типов byte, short, char. Они преобразуются в тип int, а может быть, и в тип long, если другой операнд типа long. Операнд типа int повышается до типа long, если другой операнд типа long. Конечно, числовое значение операнда при этом не меняется. Это правило приводит иногда к неожиданным результатам.

Все это мы подробно рассмотрим на практических примерах.

Стоит упомянуть, что при делении на ноль возникает исключительная ситуация, которая при отсутствии обработки этой исключительной ситуации (ArithmeticException) приводит к аварийному завершению работы программы.

Так же надо иметь в виду, что переполнение не является исключительной ситуацией, лишние старшие биты просто выкидываются.

Операция деление по модулю (или остаток) определяется так:
a % b = a — (a / b) * b

То есть 5%3=2, потому что 5-(5/3)*3=2, имейте в виду что 5/3 в данном случае это целочисленное деление и результат этой операции равен 1.

Все операции кроме инкремента и декремента имеют короткую форму записи, представленную в левой колонке. С подобным мы уже сталкивались в типе boolean.

И еще стоит упомянуть об унарном операторе минус (). Если «» используется как унарный оператор перед отдельным операндом, он выполняет унарную инверсию знака. Другими словами, он переводит положительное значение в эквивалентное ему отрицательное, и наоборот.

I00002Ну а теперь, чтобы не было скучно, простенький пример на пройденную тему.

Программа очень простая, единственное что может потребовать объяснение это пример переполнения, хотя в полном смысле этого слова, это нельзя назвать переполнением, так как значение не вышло за пределы байта, а просто произошло инвертирование числа в отрицательное значение. Можно было бы создать цикл чтобы догнать значение до реального переполнения, но во первых циклы мы еще не проходили, а во вторых и так понятно, даже на этом примере что происходит.

Если не понятно то в двух словах объясню. Максимальное положительное значение переменной типа byte равно 127. Что мы и задали в строке 32. Затем чтобы обмануть компилятор мы два раза сделали инкремент этого значения, то есть по идее должны были получить число 129. Если бы мы просто попытались сделать c=c+2, то компилятор и IDE показали бы нам ошибку приведения типов (можете попробовать и убедиться).  Далее мы вывели получившиеся значение, которое стало равно –127. Это произошло потому, что в старший разряд (первый слева) была помещена единица. Числу 129 соответствует двоичное значение 1000 0001.

Но для типа byte, максимальным положительным значением является 127, что соответствует двоичному числу 0111 1111. Когда в старший разряд помещается единица, то это число начинает интерпретироваться как отрицательное. То есть если двоичное число 1000 0001 интерпретировать как отрицательное, то его десятичное значение будет равно –127. Почему так?

А потому-что надо было учить информатику Smile

Теперь приведу вывод этой программы:

I00003

Если вдруг не понятно с двоичными числами, то очень рекомендую подтянуть информатику, так как далее будем обсуждать побитовые операции, а там без этого ни куда просто.

Побитовые операции в ряде случаев вещь чрезвычайно полезная и нужная.

В данном примере я рассмотрел из совмещенных операций только операции совмещенного сложения и присвоения, а так же вычитания и присвоения (строки 28 и 30), умножение, деление и деление по модулю уже не стал кодить, так как там все аналогично.

Про операцию деления по модулю еще стоит отметить одно правило:

a == (a / b) * b + (a % b)

Это так на заметку. Ну и поперли дальше!

Побитовые операции

I00004

Побитовые операторы и операторы сдвига манипулируют отдельными битами целого числа. Чаще всего побитовые операторы применяются для проверки и установки отдельных флаговых битов. Чтобы понять их поведение, необходимо понимать двоичные числа и формат дополнения, используемый для представления отрицательных чисел. Эти операторы нельзя использовать с массивами, объектами или с плавающей точкой. Операторы побитового сдвига и оператор NOT (~) так же нельзя использовать с операндами типа boolean. Все побитовые операторы за NOT, имеют совмещенные операторы присваивания. Если один из аргументов побитового оператора имеет значение long, то в результате тоже получится long. Если нет, то результат получается int. Если левый операнд оператора сдвига имеет значение long, то в результате тоже будет long. Если нет, то в результате получится int.

I00005

Арифметический сдвиг вправо это сдвиг с сохранением знака числа, то есть все освободившиеся старшие биты заполняются единицами. В логическом сдвиге вправо все немного сложнее.

Арифметический сдвиг влево бывает только один, так как там только один вариант чем заполнять младшие освободившиеся биты – это ноль.

Grabli

Побитовые операции выполняются поразрядно, после того как оба операнда будут приведены к одному типу int или long, так же как и для арифметических операций. Это обстоятельство надо учитывать, если вы применяете побитовые операции к типам byte, short и возможно char, поскольку тут тоже могут быть неожиданные эффекты.

Особенно осторожным надо быть с логическим сдвигом вправо >>>.

 

Теперь рассмотрим все эти операторы более подробно.

Побитовое НЕ (~)

Унарный оператор ~ является оператором побитового отрицания, или побитового НЕ. Он инвертирует каждый бит своего единственного операнда, преобразовывая единицы в нули и нули в единицы. Например:

I00006

Побитовое И (&)

Этот оператор объединяет два целых операнда посредством логической операции И с их отдельными битами. В результате биты устанавливаются в единицу только там, где соответствующие биты установлены в единицу в обоих операндах. Например:

I00007

Побитовое ИЛИ (|)

Этот оператор объединяет два целых операнда посредством логической операции ИЛИ с их отдельными битами. В результате биты устанавливаются в единицу только там, где соответствующие биты установлены в единицу в одном или обоих операндах. Если оба соответствующих бита в операндах равны нулю, то результат содержит нулевой бит. Например:

I00008

Побитовое исключающее ИЛИ (^)

Этот оператор объединяет два целых операнда посредством логической операции
исключающего ИЛИ с их отдельными битами. В результате биты установлены в единицу только там, где соответствующие биты в двух операндах различны. Если оба бита – нули или единицы, то результат будет содержать нулевой бит. Например:

I00009

Побитовый сдвиг влево (<<)

Оператор << сдвигает биты левого операнда влево на количество позиций, обозначенное правым операндом. Старшие биты левого операнда теряются, а справа добавляются нулевые биты. Сдвиг целого числа влево на n позиций равносилен умножению этого числа на 2n. Например:

I00010

Если левый операнд представляет тип long, то значение правого операнда должно находиться в диапазоне между 0 и 63. В противном случае левый операнд считается int, а значение правого операнда должно располагаться между 0 и 31.

Побитовый арифметический сдвиг вправо со знаком (>>)

Оператор >> сдвигает биты левого операнда вправо на количество позиций, обозначенное правым операндом. Младшие биты левого операнда сдвигаются «наружу» и теряются. Старшие биты, сдвигаемые «внутрь», идентичны изначальному старшему биту левого операнда, то есть если в старшем бите была единица, то освободившиеся старшие биты заполняются единицами, а если ноль, то нолями.  Данный прием называется расширением знака. Он применяется для сохранения знака левого операнда. Если левый операнд положительный, а правый имеет значение n, то оператор >> равносилен целочисленному делению на 2n.
Например:

I00011

Побитовый логический сдвиг вправо без знака (>>>)

Этот оператор аналогичен оператору >>, но он всегда заполняет нулями старшие биты, каким бы ни был знак левого операнда. Данный прием называется дополнением нулями. Его применяют, если левый операнд интерпретируется как значение без знака (несмотря на то что в Java все типы со знаком). Например:

I00012

Как я уже говорил, с этим оператором надо быть очень осторожным, так как надо учитывать, что его операнды автоматически приводятся к типу int (или long), то есть их разрядность увеличивается. Приведенный пример справедлив для типа данных int, а вот для byte и short, он уже может подглючить. Поэтому пример с логическим сдвигом вправо мы рассмотрим более подробно, отдельно от остальных.

И еще несколько небольших замечаний:

  • В побитовых операциях используется по умолчанию тип int, то есть компилятор java всегда воспринимает правый операнд как int, поэтому при операциях с byte и short необходимо пользоваться операцией приведения типов. Мы это еще пока не проходили, но скоро пройдем, а пока мотаем на ус.
  • Это же справедливо и для арифметических операций, как я уже говорил, происходит автоматическое приведение всех операндов к типу int или long.

Ну а теперь немножко практики.

В библиотеку ProJava.jar я добавил еще парочку статических методов printlnByte и printByte для вывода значений типа byte в двоичном виде как строку.

И далее то что это программа выводит

I00013

Тут все более или менее просто и понятно. Далее будем разбираться с оператором логического сдвига вправо.

Теперь посмотрим на вывод этой программы:

I00014

Как видим для типа int все идет так как написано, то есть старшие биты заполняются нужным количеством нулей. А вот для типа byte, уже начинаются глюки.
Строка 32 выводит нам десятичное значение переменной типа byte b1. И это значение равно 1073741811. Не хило так для типа byte, у которого максимальное положительное значение это 127. Это произошло потому, что, как уже говорилось, при арифметических и побитовых операциях происходит автоматическое приведение типов byte и short до типа int (или long). Вот на это мы и напоролись в 32 строке.

39 строка, которая выводит двоичное значение переменной типа byte b1 тоже преподнесла нам сюрприз. По идее в двух старших битах должны быть два нуля, но мы их там не наблюдаем. Там пара единиц. Это опять же произошло все по той же причине автоматического расширение byte и short до int или long, если long присутствует в выражении.

Приведение типов, явное и неявное мы рассмотрим чуток позже, а пока полезно знать о граблях неявного приведения типов.

И так в строке 39 мы просто получили обрубок значения int, старшие разряды которого были выкинуты, вместе с двумя нулями в начале (смотрите внимательно скриншот вывода).

Теперь поправим эту ситуацию, чтобы все работало правильно. Для этого надо применить маску 0xff, которая превратит все старшие 24 разряда типа int в нули.

Исправленный вариант:

и далее вывод программы (только новая часть):

I00015

Ну и напоследок приведу табличку побитовых операций (не сдвиговых, т.к. это не имеет смысла)

I00016

В принципе это тема весьма нужная и полезная и развлекаться с примерами здесь можно очень долго, но нам надо двигаться вперед.

Grabli

Хотя про еще одни грабли все таки стоит упомянуть. Это опять же касается операций сдвига. Перед тем как совершать любой побитовый сдвиг (например, x>>y) виртуальная машина java вычисляет остаток от деления y на ширину типа данных (для int 32 (y%32), для long 64 (y%64) и т.д.) и производит сдвиг на величину этого остатка. Поэтому, например, если мы задумаем сдвинуть значение типа int на 32 бита или 64 бита и т.п., каким либо оператором сдвига, то значение x останется прежним, потому что 32%32=0 (64%32=0).

А теперь, чтобы стало понятнее рассмотрим это на примере:

I00017

И вывод этой программы на консоль:

I00018

Как видим, там где мы сдвигали на 32 разряда, ни какого сдвига на самом деле не произошло по описанной выше причине.

Кроме того, при сдвиге на 48 позиций вправо, сдвиг на самом деле произошел на 16 позиций, по той же причине.

То есть можно заключить, что двигать переменную на количество бит равное или большее ее ширине в битах просто не имеет смысла, так как в этом случае сдвига или вовсе не будет или же будет на количество бит равное делению по модулю количества сдвигаемых бит, на ширину сдвигаемого типа.