ZetCode

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.

b1.f90
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.

b2.f90
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.

b3.f90
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.

b4.f90
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.

b5.f90
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.

b6.f90
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.

b7.f90
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)

GNU Fortran manual

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.