Особый класс операций представляют поразрядные операции. Они выполняются над отдельными разрядами числа. Они выполняются только над целыми числами (то есть над значениями типов byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint и unativeint.).
При данных операциях числа рассматриваются в двоичном представлении, например, 2 в двоичном представлении 10 и имеет два разряда, число 7 - 111 и имеет три разряда.
Рассмотрим все поразрядные операции языка F#:
&&&(логическое умножение)
Умножение производится поразрядно, и если у обоих операндов значения соответствующих разрядов равно 1, то операция возвращает 1, иначе возвращается число 0. Например:
let x1 = 2 //010 let y1 = 5 //101 let z1 = x1 &&& y1 //000 printfn $"z1 = {z1}" // z1 = 0 let x2 = 4 //100 let y2 = 5 //101 let z2 = x2 &&& y2 //100 printfn $"z2 = {z2}" // z2 = 4
В первом случае у нас два числа 2 и 5. 2 в двоичном виде представляет число 010, а 5 - 101. Поразрядно умножим числа (0*1, 1*0, 0*1) и в итоге получим 000.
Во втором случае у нас вместо двойки число 4, у которого в первом разряде 1, так же как и у числа 5, поэтому в итоге получим (1*1, 0*0, 0 *1) = 100, то есть число 4 в десятичном формате.
||| (логическое сложение)
Похоже на логическое умножение, операция также производится по двоичным разрядам, но теперь возвращается единица, если хотя бы у одного числа в данном разряде имеется единица. Например:
let x1 = 2 //010 let y1 = 5 //101 let z1 = x1 ||| y1 //111 printfn $"z1 = {z1}" // z1 = 7 let x2 = 4 //100 let y2 = 5 //101 let z2 = x2 ||| y2 //101 printfn $"z2 = {z2}" // z2 = 5
^^^ (логическое исключающее ИЛИ). Также эту операцию называют XOR. Если значения соответствующих разрядов обоих чисел разные, то возвращается 1, если одинаковые - возвращается 0:
let x1 = 2 //010 let y1 = 5 //101 let z1 = x1 ^^^ y1 //111 printfn $"z1 = {z1}" // z1 = 7 let x2 = 4 //100 let y2 = 5 //101 let z2 = x2 ^^^ y2 //001 printfn $"z2 = {z2}" // z2 = 1
~~~ (логическое отрицание или инверсия)
Еще одна поразрядная операция, которая инвертирует все разряды: если значение разряда равно 1, то оно становится равным нулю, и наоборот.
let x = 12 // 00001100 let z = ~~~x // 11110011 или -13 printfn $"z = {z}" // z = -13
Для записи чисел со знаком в F# применяется дополнительный код (two’s complement), при котором старший разряд является знаковым. Если его значение равно 0, то число положительное, и его двоичное представление не отличается от представления беззнакового числа. Например, 0000 0001 в десятичной системе 1.
Если старший разряд равен 1, то мы имеем дело с отрицательным числом. Например, 1111 1111 в десятичной системе представляет -1. Соответственно, 1111 0011 представляет -13.
Чтобы получить из положительного числа отрицательное, его нужно инвертировать и прибавить единицу:
let x = 12 // 00001100 let z = ~~~x // 11110011 или -13 let d = z + 1 printfn $"d = {d}" // d = -12
Операции сдвига также производятся над разрядами чисел. Сдвиг может происходить вправо и влево.
x<<<y
- сдвигает число x влево на y разрядов. Например:
let x = 4 // 100 let z = x <<< 2 // сдвиг на 2 разряда влево - 10000 printfn $"z = {z}" // z = 16
Здесь операция сдвигает число 4 (которое в двоичном представлении 100) на два разряда влево, то есть в итоге получается 10000 или число 16 в десятичном представлении.
x>>>y
- сдвигает число x вправо на y разрядов. Например:
let x = 16 // 10000 let z = x >>> 1 // сдвиг на 1 разряд вправо - 1000 printfn $"z = {z}" // z = 8
Здесь операция сдвигает число 16 (которое в двоичном представлении 10000) на один разряд вправо, то есть в итоге получается 1000 или число 8 в десятичном представлении.
Таким образом, если исходное число, которое надо сдвинуть в ту или другую строну, делится на два, то фактически получается умножение или деление на два. Поэтому подобную операцию можно использовать вместо непосредственного умножения или деления на два.