Fortran bit manipulation
last modified November 14, 2026
An integer is stored as a row of bits, each holding 0 or
1. A default integer occupies 32 of them, and the number 12 is the row
00000000000000000000000000001100: 8 + 4. Negative values use
two's complement, where the leftmost bit carries a negative weight, so
-13 is the complement of 13 plus one. Fortran exposes this representation
through a set of intrinsic procedures, and a program that works with flags,
colours, image formats, checksums or compression soon needs them.
This article prints values as binary with the b edit descriptor,
combines bits with iand, ior, ieor and
not, shifts them with ishft and ishftc,
sets and clears single flags, counts the set bits, packs three colour channels
into one integer, computes a checksum, and generates the Gray code, a sequence
in which neighbours differ by exactly one bit.
Binary output and the logical operators
The b, o and z edit descriptors print an
integer in base 2, 8 and 16, and they arrived with Fortran 2008. The form
b32.32 asks for 32 binary digits with at least 32 of them, which
is the full width of a default integer; without the .32 the
leading zeros disappear. The four logical operators work bit by bit:
iand keeps the bits set in both operands, ior the
bits set in either, ieor the bits set in exactly one, and
not inverts every bit.
program b1
implicit none
integer :: a, b
a = 12
b = 10
print '(a, i3, a, b32.32)', 'a = ', a, ' = ', a
print '(a, i3, a, b32.32)', 'b = ', b, ' = ', b
print '(a, i3, a, b32.32)', 'iand(a, b) = ', iand(a, b), ' = ', &
iand(a, b)
print '(a, i3, a, b32.32)', 'ior(a, b) = ', ior(a, b), ' = ', &
ior(a, b)
print '(a, i3, a, b32.32)', 'ieor(a, b) = ', ieor(a, b), ' = ', &
ieor(a, b)
print '(a, i3, a, b32.32)', 'not(a) = ', not(a), ' = ', not(a)
print '(a, i3, a, b32.32)', 'not(0) = ', not(0), ' = ', not(0)
print '(a, l1)', 'bit 3 of a set: ', btest(a, 3)
print '(a, l1)', 'bit 0 of a set: ', btest(a, 0)
print '(a, i0)', 'bits 1 to 3 of a: ', ibits(a, 1, 3)
print '(a, b32.32)', 'the same field: ', ibits(a, 1, 3)
end program b1
Twelve is 1100 and ten is 1010, so their conjunction
keeps only the shared 8, their disjunction gives 14, and their exclusive or
gives 6, the bits where they differ. The complement of twelve is -13, printed
as twenty-eight ones followed by 0011: the leading ones are the sign extension
of a negative number, and not(0) is -1 because every bit is set. The
tests btest(a, 3) and btest(a, 0) ask about a single
bit by position, counting from zero at the right, and ibits(a, 1, 3)
copies three bits starting at position 1, which is the number 6.
$ gfortran b1.f90 -o b1 $ ./b1 a = 12 = 00000000000000000000000000001100 b = 10 = 00000000000000000000000000001010 iand(a, b) = 8 = 00000000000000000000000000001000 ior(a, b) = 14 = 00000000000000000000000000001110 ieor(a, b) = 6 = 00000000000000000000000000000110 not(a) = -13 = 11111111111111111111111111110011 not(0) = -1 = 11111111111111111111111111111111 bit 3 of a set: T bit 0 of a set: F bits 1 to 3 of a: 6 the same field: 00000000000000000000000000000110
Shifting and rotating
ishft(value, n) moves the bits of value by n
places, to the left when n is positive and to the right when it is
negative, and fills the empty positions with zeros. A left shift by
k multiplies by 2**k, which is why a compiler turns
multiplication by a power of two into a shift. A right shift divides by
2**k and discards the remainder, but only for non-negative values:
the vacated bits are filled with zeros, so a negative value becomes a large
positive one. ishftc is the circular version, and its third
argument limits the operation to a field of that many bits.
program b2
implicit none
integer :: k, n
print *, 'left shift of one:'
do k = 0, 10
print '(a, i2, a, i6, a, b16.16)', 'ishft(1, ', k, ') = ', &
ishft(1, k), ' ', ishft(1, k)
end do
print '(a, i0)', 'ishft(1024, -5) = ', ishft(1024, -5)
print '(a, i0)', 'ishft(7, -1) = ', ishft(7, -1)
print '(a, i0)', 'ishft(1, -1) = ', ishft(1, -1)
print '(a, i0)', 'ishft(-8, -1) = ', ishft(-8, -1)
print '(a, i0)', 'ishft(-1, 1) = ', ishft(-1, 1)
n = 13
print '(a, i0, a, i0)', '13 * 8 = ', n * 8, ', ishft(13, 3) = ', &
ishft(n, 3)
print '(a, i0)', 'ishftc(1, 1, 8) = ', ishftc(1, 1, 8)
print '(a, i0)', 'ishftc(128, 1, 8) = ', ishftc(128, 1, 8)
print '(a, i0)', 'ishftc(129, 1, 8) = ', ishftc(129, 1, 8)
print '(a, i0)', 'ishftc(129, -1, 8)= ', ishftc(129, -1, 8)
print '(a, b16.16)', 'ishftc(1, -1, 8) = ', ishftc(1, -1, 8)
end program b2
The first table is the sequence of powers of two, each one a single 1 that
moves left. Shifting 1024 right by five returns 32, and 7 right by one returns
3, the integer part of the division. Negative values behave differently:
ishft(-8, -1) is 2147483644, because a zero enters at the left and
the sign disappears; the result is not -4. Rotation keeps every bit:
ishftc(129, 1, 8) moves the top bit of the eight-bit field to the
bottom and gives 3, while the same call with -1 gives 192.
$ gfortran b2.f90 -o b2 $ ./b2 left shift of one: ishft(1, 0) = 1 0000000000000001 ishft(1, 1) = 2 0000000000000010 ishft(1, 2) = 4 0000000000000100 ishft(1, 3) = 8 0000000000001000 ishft(1, 4) = 16 0000000000010000 ishft(1, 5) = 32 0000000000100000 ishft(1, 6) = 64 0000000001000000 ishft(1, 7) = 128 0000000010000000 ishft(1, 8) = 256 0000000100000000 ishft(1, 9) = 512 0000001000000000 ishft(1, 10) = 1024 0000010000000000 ishft(1024, -5) = 32 ishft(7, -1) = 3 ishft(1, -1) = 0 ishft(-8, -1) = 2147483644 ishft(-1, 1) = -2 13 * 8 = 104, ishft(13, 3) = 104 ishftc(1, 1, 8) = 2 ishftc(128, 1, 8) = 1 ishftc(129, 1, 8) = 3 ishftc(129, -1, 8)= 192 ishftc(1, -1, 8) = 0000000010000000
Flags in a single integer
A set of yes-or-no options fits into one integer, one bit per option. The
procedure ibset(mode, n) returns the value with bit n
set, ibclr returns it with the bit cleared, and
btest reports the current state. Toggling is an exclusive or with
2**n. The example models the read, write and execute permissions
of a file, which are exactly the three low bits of a Unix mode.
module permissions
implicit none
contains
function letters(mode) result(s)
integer, intent(in) :: mode
character(len=3) :: s
s = '---'
if (btest(mode, 0)) s(1:1) = 'r'
if (btest(mode, 1)) s(2:2) = 'w'
if (btest(mode, 2)) s(3:3) = 'x'
end function letters
subroutine show(what, mode)
character(len=*), intent(in) :: what
integer, intent(in) :: mode
print '(a, 1x, b3.3, a, i0, a, a)', what, mode, ' (', mode, ') ', &
letters(mode)
end subroutine show
end module permissions
program b3
use permissions
implicit none
integer, parameter :: read_bit = 0, write_bit = 1, execute_bit = 2
integer :: mode
mode = 0
call show('nothing set', mode)
mode = ibset(mode, read_bit)
call show('after ibset read', mode)
mode = ibset(mode, write_bit)
call show('after ibset write', mode)
mode = ibset(mode, execute_bit)
call show('after ibset exec', mode)
mode = ibclr(mode, write_bit)
call show('after ibclr write', mode)
mode = ieor(mode, 2**read_bit)
call show('after toggle read', mode)
print '(a, l1)', 'write allowed: ', btest(mode, write_bit)
print '(a, l1)', 'read allowed: ', btest(mode, read_bit)
end program b3
The mode climbs from 0 to 7 as the three bits are set, and the three letters
appear in the same order. Clearing the write bit gives 5, which reads as
r-x, and the exclusive or with 1 removes the read bit and leaves
--x. The two final tests confirm that the bits were really
cleared, not merely hidden by the printing function. Setting a bit that is
already set changes nothing, which makes ibset safe to call
without a test.
$ gfortran b3.f90 -o b3 $ ./b3 nothing set 000 (0) --- after ibset read 001 (1) r-- after ibset write 011 (3) rw- after ibset exec 111 (7) rwx after ibclr write 101 (5) r-x after toggle read 100 (4) --x write allowed: F read allowed: F
Counting bits
Four inquiries describe the bit pattern as a whole. bit_size
returns the width of a kind in bits, popcnt counts the set bits,
and leadz and trailz count the zero bits at the two
ends. The population count of the exclusive or of two values is their
Hamming distance, the number of positions in which they differ, and
that single expression is the basis of error-detecting codes and of
similarity measures on binary data.
program b4
use iso_fortran_env, only: int8
implicit none
integer :: a, b, i
print '(a, i0)', 'bit_size of a default integer: ', bit_size(0)
print '(a, i0)', 'bit_size of an int8: ', bit_size(0_int8)
print *, 'value binary set bits'
do i = 0, 5
print '(i4, 4x, b32.32, i6)', i, i, popcnt(i)
end do
print '(a, i0)', 'popcnt(255) = ', popcnt(255)
print '(a, i0)', 'popcnt(-1) = ', popcnt(-1)
print '(a, i0)', 'popcnt of an int8 -1: ', popcnt(int(-1, int8))
print '(a, i0)', 'leadz(1) = ', leadz(1)
print '(a, i0)', 'leadz(0) = ', leadz(0)
print '(a, i0)', 'leadz(-1) = ', leadz(-1)
print '(a, i0)', 'trailz(8) = ', trailz(8)
print '(a, i0)', 'trailz(0) = ', trailz(0)
print '(a, i0)', 'trailz(-8) = ', trailz(-8)
a = 12
b = 10
print '(a, i0)', 'hamming distance 12 to 10: ', popcnt(ieor(a, b))
end program b4
A default integer is 32 bits wide and an int8 is eight, so
popcnt(-1) is 32 while the same value stored in an
int8 has eight set bits. leadz(1) is 31 because the
single one sits at the far right, and leadz(0) is the full width.
The trailing zeros of 8 are three, and the same count for -8 is also three,
since the low bits of the two's complement do not change. Twelve and ten differ
in two positions, which is their Hamming distance.
$ gfortran b4.f90 -o b4 $ ./b4 bit_size of a default integer: 32 bit_size of an int8: 8 value binary set bits 0 00000000000000000000000000000000 0 1 00000000000000000000000000000001 1 2 00000000000000000000000000000010 1 3 00000000000000000000000000000011 2 4 00000000000000000000000000000100 1 5 00000000000000000000000000000101 2 popcnt(255) = 8 popcnt(-1) = 32 popcnt of an int8 -1: 8 leadz(1) = 31 leadz(0) = 32 leadz(-1) = 0 trailz(8) = 3 trailz(0) = 32 trailz(-8) = 3 hamming distance 12 to 10: 2
Packing fields into one integer
Three bytes fit into one integer, and this is how a colour is usually stored:
eight bits of red, eight of green and eight of blue, read from the top down.
Packing shifts each channel into its place and combines them with
ior; unpacking masks the other channels out with
iand and shifts the wanted one down. ibits does both
steps at once. The hex descriptor z8.8 prints the result in the
form a colour is normally written, and a BOZ literal such as
z'FFFF00FF' supplies a mask in the same notation.
program b5
implicit none
integer :: red, green, blue, colour
red = 255
green = 128
blue = 64
colour = ior(ishft(red, 16), ior(ishft(green, 8), blue))
print '(a, z8.8)', 'packed colour: ', colour
print '(a, b32.32)', 'in binary: ', colour
print '(a, i0)', 'as a number: ', colour
print '(a, i0)', 'red channel: ', iand(ishft(colour, -16), 255)
print '(a, i0)', 'green channel: ', iand(ishft(colour, -8), 255)
print '(a, i0)', 'blue channel: ', iand(colour, 255)
print '(a, i0)', 'red by ibits: ', ibits(colour, 16, 8)
colour = ior(iand(colour, z'FFFF00FF'), ishft(200, 8))
print '(a, z8.8)', 'green now 200: ', colour
print '(a, i0)', 'green channel: ', ibits(colour, 8, 8)
end program b5
Red 255, green 128 and blue 64 pack into 00FF8040, which is
16744512 in decimal. The three extraction lines return the channels in the
order they were packed, and ibits(colour, 16, 8) gives the red
channel without an explicit shift. Replacing the green channel uses the mask
FFFF00FF, which keeps every bit except the middle byte, and then
inserts 200 shifted into place; the result is 00FFC840. The same
pattern packs dates, pixel formats and instruction fields.
$ gfortran b5.f90 -o b5 $ ./b5 packed colour: 00FF8040 in binary: 00000000111111111000000001000000 as a number: 16744512 red channel: 255 green channel: 128 blue channel: 64 red by ibits: 255 green now 200: 00FFC840 green channel: 200
A rotating checksum
A checksum reduces a message to a short number that changes when the message changes. The simplest one adds the character codes, and it fails at once: an anagram has the same sum. Mixing exclusive or with a rotation does better, because the rotation moves each character to a new position and the order of the characters then matters. The function below rotates the accumulator by one bit after every character, so the same letters in a different order give a different result.
module checksums
implicit none
contains
integer function rotxor(text) result(acc)
character(len=*), intent(in) :: text
integer :: i
acc = 0
do i = 1, len_trim(text)
acc = ishftc(ieor(acc, iachar(text(i:i))), 1)
end do
end function rotxor
integer function addsum(text) result(acc)
character(len=*), intent(in) :: text
integer :: i
acc = 0
do i = 1, len_trim(text)
acc = acc + iachar(text(i:i))
end do
end function addsum
end module checksums
program b6
use checksums
implicit none
character(len=*), parameter :: words(4) = [character(len=8) :: &
'fortran', 'Fortran', 'nartrof', 'abcdefg']
integer :: i
print '(a8, 2x, a8, 2x, a8)', 'text', 'rotxor', 'addsum'
do i = 1, size(words)
print '(a8, 2x, z8.8, 2x, i8)', trim(words(i)), &
rotxor(trim(words(i))), addsum(trim(words(i)))
end do
print '(a, z8.8)', 'one character more: ', rotxor('fortran!')
end program b6
fortran and nartrof have the same plain sum, 764, but
their rotating checksums are 00002308 and 000024A0.
Changing the capital letter changes the sum by 32, and adding one character at
the end changes the checksum completely, to 00004652, because the
rotations of all earlier characters were applied one more time. A checksum of
this kind detects accidental changes; it is not a cryptographic hash, since
anyone can construct a message with a chosen value.
$ gfortran b6.f90 -o b6
$ ./b6
text rotxor addsum
fortran 00002308 764
Fortran 00003308 732
nartrof 000024A0 764
abcdefg 0000205E 700
one character more: 00004652
Gray code
The binary numbers from 0 to 15 change several bits at each step: the step
from 3 to 4 flips three of them. The Gray code is an ordering of the
same numbers in which consecutive values differ in exactly one bit, obtained
with g = ieor(n, ishft(n, -1)). Such an ordering matters when a
value is read while it changes, because a single-bit change can never produce
a reading that is completely wrong. The inverse conversion shifts and
exclusive-ors the value repeatedly with growing steps.
module grays
implicit none
contains
integer function to_gray(n) result(g)
integer, intent(in) :: n
g = ieor(n, ishft(n, -1))
end function to_gray
integer function from_gray(g) result(n)
integer, intent(in) :: g
integer :: shift
n = g
shift = 1
do while (shift < 16)
n = ieor(n, ishft(n, -shift))
shift = shift * 2
end do
n = iand(n, 15)
end function from_gray
end module grays
program b7
use grays
implicit none
integer :: n, g, previous, single
print '(a)', ' n binary gray bits changed'
previous = 0
single = 0
do n = 0, 15
g = to_gray(n)
if (n > 0) then
if (popcnt(ieor(g, previous)) == 1) single = single + 1
end if
print '(i3, 4x, b4.4, 4x, b4.4, i10)', n, n, g, &
popcnt(ieor(g, previous))
previous = g
end do
print '(a, i0, a, i0)', 'consecutive pairs one bit apart: ', &
single, ' of 15'
print '(a, i0)', 'from_gray(to_gray(11)) = ', from_gray(to_gray(11))
end program b7
Each row shows the number, its ordinary binary form and its Gray code, and the
last column counts the bits that changed from the previous row. Every one of
the fifteen steps changes exactly one bit, which the program verifies rather
than assumes. from_gray(to_gray(11)) returns 11, so the conversion
is reversible. The same construction generates the sequence a mechanical
encoder uses, and the bit-reversal permutation that the fast Fourier transform
needs is built from the same operations.
$ gfortran b7.f90 -o b7 $ ./b7 n binary gray bits changed 0 0000 0000 0 1 0001 0001 1 2 0010 0011 1 3 0011 0010 1 4 0100 0110 1 5 0101 0111 1 6 0110 0101 1 7 0111 0100 1 8 1000 1100 1 9 1001 1101 1 10 1010 1111 1 11 1011 1110 1 12 1100 1010 1 13 1101 1011 1 14 1110 1001 1 15 1111 1000 1 consecutive pairs one bit apart: 15 of 15 from_gray(to_gray(11)) = 11
Next steps
The data types article explains the kinds
whose widths bit_size reports, and the
literals article covers the BOZ constants
used for masks. The operators article gives
the precedence of the arithmetic that surrounds these calls, and the
complex numbers article shows another
way in which one value can carry two numbers. The
formatted output article explains the
b, o and z descriptors used throughout.
Source
Fortran 2018 standard (J3/18-007r1)
Fortran-lang: learning resources
In this article we have printed integers as binary with the b
descriptor, combined bits with the four logical operators, shifted and rotated
them with ishft and ishftc, stored three permissions
in one integer with ibset and ibclr, counted set
bits and Hamming distances, packed and unpacked an RGB colour, built a rotating
checksum from ieor and ishftc, and generated the Gray
code with a one-bit adjacency check. The previous article,
Fortran complex numbers, works with
values that carry two reals instead of thirty-two bits.
Author
List all Fortran tutorials.