ZetCode

Perl bitwise operators

last modified November 7, 2026

Bitwise operators work on the individual bits of integers. They are used for flags and permission masks, for extracting fields from packed values such as colours, IP addresses and binary file headers, and for low-level tricks such as testing whether a number is a power of two. Perl has the following bitwise operators:

Each operator also has an assignment form, such as |= or <<=. The bitwise operators should not be confused with the logical operators &&, || and !, which work with truth values. The examples were run with 64-bit Perl 5.44 on Linux.

AND, OR, XOR and NOT

The %b format of printf shows a number in binary, which makes the effect of the operators visible.

basic.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my ($x, $y) = (0b1100_1010, 0b0101_0110);

printf "x      %08b  %3d\n", $x, $x;
printf "y      %08b  %3d\n", $y, $y;
printf "x & y  %08b  %3d\n", $x & $y, $x & $y;
printf "x | y  %08b  %3d\n", $x | $y, $x | $y;
printf "x ^ y  %08b  %3d\n", $x ^ $y, $x ^ $y;
printf "~x     %08b  %3d\n", ~$x & 0xff, ~$x & 0xff;

The numbers are written as binary literals; underscores can separate digit groups. The %08b format pads the result to eight digits with zeros. AND keeps the bits common to both numbers, OR combines them, and XOR keeps the bits in which they differ. The ~ operator inverts all 64 bits of the number, so we mask the result with & 0xff to keep only the lowest eight.

$ perl basic.pl
x      11001010  202
y      01010110   86
x & y  01000010   66
x | y  11011110  222
x ^ y  10011100  156
~x     00110101   53

Shift operators

Shifting left by n bits multiplies a number by 2n; shifting right divides it by 2n and drops the remainder. Together with masks, shifts select groups of bits.

shift.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $n = 13;

for my $i (0 .. 4) {
    printf "%2d << %d = %4d   %2d >> %d = %2d\n",
        $n, $i, $n << $i, $n, $i, $n >> $i;
}

say "1 << 40 = ", 1 << 40;
say "bit 5 of 13: ", (13 >> 5) & 1;
say "bit 2 of 13: ", (13 >> 2) & 1;

my $rgb = 0x3a7fc0;
my ($r, $g, $b) = (($rgb >> 16) & 0xff, ($rgb >> 8) & 0xff, $rgb & 0xff);
say "rgb($r, $g, $b)";
printf "#%06x\n", ($r << 16) | ($g << 8) | $b;

To read bit n, we shift the number right by n places and take the lowest bit with & 1; 13 is 1101 in binary, so bit 2 is set. The second part splits an RGB colour stored in one integer into its three bytes: the red component is in bits 16–23, so we shift by 16 and mask with 0xff. Shifting the components back and combining them with OR rebuilds the colour.

$ perl shift.pl
13 << 0 =   13   13 >> 0 = 13
13 << 1 =   26   13 >> 1 =  6
13 << 2 =   52   13 >> 2 =  3
13 << 3 =  104   13 >> 3 =  1
13 << 4 =  208   13 >> 4 =  0
1 << 40 = 1099511627776
bit 5 of 13: 0
bit 2 of 13: 1
rgb(58, 127, 192)
#3a7fc0

Bit flags

Flags store several yes/no options in one integer, each option in its own bit. Setting, clearing, toggling and testing a flag each take a single operator.

flags.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

use constant {
    READ => 1 << 0,
    WRITE => 1 << 1,
    EXEC => 1 << 2,
    ADMIN => 1 << 3,
};

my %names = (READ, 'read', WRITE, 'write', EXEC, 'exec', ADMIN, 'admin');

sub describe {
    my $flags = shift;
    my @set = map { $names{$_} } grep { $flags & $_ } sort { $a <=> $b } keys %names;
    return sprintf '%04b [%s]', $flags, join ',', @set;
}

my $perm = READ | WRITE;
printf "%-14s%s\n", "start:", describe($perm);

$perm |= EXEC;
printf "%-14s%s\n", "add exec:", describe($perm);

$perm &= ~WRITE;
printf "%-14s%s\n", "drop write:", describe($perm);

$perm ^= ADMIN;
printf "%-14s%s\n", "toggle admin:", describe($perm);

$perm ^= ADMIN;
printf "%-14s%s\n", "toggle admin:", describe($perm);

say "can read" if $perm & READ;
say "cannot write" unless $perm & WRITE;

my $need = READ | EXEC;
say "has read and exec" if ($perm & $need) == $need;

Each constant is a different power of two, created with 1 << n. The idioms are:

The parentheses in the last test are required, because == binds more tightly than &. The describe function lists the names of the flags that are set.

$ perl flags.pl
start:        0011 [read,write]
add exec:     0111 [read,write,exec]
drop write:   0101 [read,exec]
toggle admin: 1101 [read,exec,admin]
toggle admin: 0101 [read,exec]
can read
cannot write
has read and exec

Unix file mode bits

Unix file permissions are a real-world bit field. The third value returned by stat holds the file type and the permission bits.

mode.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

use Fcntl qw(:mode);

for my $path ('/tmp', '/etc/passwd', '/usr/bin/passwd') {
    my $mode = (stat $path)[2] // die "Cannot stat $path: $!";

    my $type = S_ISDIR($mode) ? 'dir' : S_ISREG($mode) ? 'file' : 'other';
    my $perms = $mode & 07777;

    my $str = '';
    for my $shift (6, 3, 0) {
        my $bits = ($perms >> $shift) & 7;
        $str .= ($bits & 4 ? 'r' : '-') . ($bits & 2 ? 'w' : '-')
            . ($bits & 1 ? 'x' : '-');
    }

    my @special;
    push @special, 'setuid' if $mode & S_ISUID;
    push @special, 'sticky' if $mode & S_ISVTX;

    printf "%-16s %-5s %04o %s %s\n", $path, $type, $perms, $str, "@special";
}

The Fcntl module exports the :mode constants and the S_ISDIR and S_ISREG type tests. The mask 07777 keeps the permission bits and the special bits, which are printed in octal with %04o. Each group of three bits holds the read, write and execute permission for the owner, the group and others; we shift each group down and test its bits. The S_ISUID and S_ISVTX constants test the setuid and sticky bits.

$ perl mode.pl
/tmp             dir   1777 rwxrwxrwx sticky
/etc/passwd      file  0644 rw-r--r-- 
/usr/bin/passwd  file  4755 rwxr-xr-x setuid

Negation and use integer

Perl's bitwise operators work on unsigned 64-bit integers by default. This affects ~ and the right shift of negative numbers. The integer pragma switches them to signed arithmetic.

negate.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

say ~0;
printf "%x\n", ~0;
say ~5;
say ~5 & 0xff;

{
    use integer;
    say ~5;
    say -16 >> 2;
}

say -16 >> 2;

~0 is the largest unsigned 64-bit number, with all bits set, and ~5 is a huge number rather than -6. Masking with 0xff gives the expected 8-bit result. Inside the block with use integer, ~5 is -6 in two's complement, and -16 >> 2 is an arithmetic shift that keeps the sign, giving -4. Outside the block, -16 is treated as a large unsigned number and the shift produces a positive result. The pragma is lexically scoped, so it applies only inside its block.

$ perl negate.pl
18446744073709551615
ffffffffffffffff
18446744073709551610
250
-6
-4
4611686018427387900

Counting bits and powers of two

Two classic tricks use the expression $n & ($n - 1), which clears the lowest set bit of a number.

count.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

sub popcount {
    my $n = shift;
    my $count = 0;

    while ($n) {
        $n &= $n - 1;
        $count++;
    }

    return $count;
}

for my $n (0, 1, 7, 255, 1024, 0xdeadbeef) {
    printf "%10d  %2d  %2d\n", $n, popcount($n), unpack '%32b*', pack 'Q', $n;
}

for my $n (1, 6, 64, 100) {
    my $pow2 = $n && !($n & ($n - 1));
    say "$n is", ($pow2 ? '' : ' not'), " a power of two";
}

The popcount function counts the set bits by clearing the lowest one until the number is zero, so the loop runs once per set bit. The third column computes the same value with unpack: pack 'Q' stores the number as eight bytes, and the %32b* template returns the checksum of all bits, which is their count. A power of two has exactly one bit set, so clearing it gives zero; the test also checks that the number is not zero itself.

$ perl count.pl
         0   0   0
         1   1   1
         7   3   3
       255   8   8
      1024   1   1
3735928559  24  24
1 is a power of two
6 is not a power of two
64 is a power of two
100 is not a power of two

Subnet calculations

An IPv4 address is a 32-bit number, and a network prefix such as /22 is a mask with the top 22 bits set. Network calculations are therefore just AND, OR and NOT.

subnet.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

use Socket qw(inet_aton inet_ntoa);

sub ip2int { unpack 'N', inet_aton(shift) }
sub int2ip { inet_ntoa(pack 'N', shift) }

my ($ip, $prefix) = split m{/}, shift // '192.168.10.77/22';

my $addr = ip2int($ip);
my $mask = (0xffffffff << (32 - $prefix)) & 0xffffffff;
my $net = $addr & $mask;
my $bcast = $net | (~$mask & 0xffffffff);

say "address:   ", int2ip($addr);
say "netmask:   ", int2ip($mask);
say "network:   ", int2ip($net);
say "broadcast: ", int2ip($bcast);
say "first:     ", int2ip($net + 1);
say "last:      ", int2ip($bcast - 1);
say "hosts:     ", $bcast - $net - 1;

for my $other ('192.168.11.200', '192.168.12.1') {
    my $in = (ip2int($other) & $mask) == $net;
    say "$other is ", ($in ? 'inside' : 'outside'), " the network";
}

The inet_aton function from the Socket module turns the dotted address into four bytes, and unpack 'N' reads them as a 32-bit big-endian number. The mask is built by shifting all-ones left by the number of host bits; because Perl uses 64-bit integers, the result is masked to 32 bits. The network address is the address AND the mask, and the broadcast address is the network OR the inverted mask. An address belongs to the network if masking it gives the network address.

$ perl subnet.pl
address:   192.168.10.77
netmask:   255.255.252.0
network:   192.168.8.0
broadcast: 192.168.11.255
first:     192.168.8.1
last:      192.168.11.254
hosts:     1022
192.168.11.200 is inside the network
192.168.12.1 is outside the network

String bitwise operators

When both operands are strings, the bitwise operators can also work on the bytes of the strings. Since Perl 5.28 the bitwise feature, enabled by use v5.28 or later, separates the two meanings: &, |, ^ and ~ always treat their operands as numbers, while &., |., ^. and ~. always treat them as strings.

strings.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

say "12" & "10";
say "12" &. "10";
say "AB" |. "  ";
say "Hello" ^. (" " x 5);

my $key = 'k3y';
my $msg = 'Attack at dawn';

my $pad = substr($key x (1 + length($msg) / length $key), 0, length $msg);
my $enc = $msg ^. $pad;
say unpack 'H*', $enc;
say $enc ^. $pad;

my ($s1, $s2) = ('perl', 'peal');
my $diff = $s1 ^. $s2;
say "differ at ", join ',', grep { substr($diff, $_, 1) ne "\0" } 0 .. length($diff) - 1;

"12" & "10" is the number 8, but "12" &. "10" ANDs the characters: '2' (0x32) AND '0' (0x30) is '0', giving the string 10. Without the feature, the result depended on whether the values had been used as strings or as numbers, which was a source of subtle bugs. In ASCII, upper and lower case letters differ only in the bit 0x20, which is the space character, so OR with spaces lowercases letters and XOR flips their case. XOR with a repeated key is a toy cipher: applying the same key twice restores the message. XORing two strings also shows where they differ, because equal bytes give a zero byte. The string operators should only be used on byte strings; they die on characters above 255.

$ perl strings.pl
8
10
ab
hELLO
2a470d0a50124b520d4b57181c5d
Attack at dawn
differ at 2

Best practices

In this article we have used Perl's bitwise operators. We have combined numbers with AND, OR, XOR and NOT, used shifts to read and build bit fields, managed bit flags, decoded Unix file modes, looked at 64-bit negation and use integer, counted bits, calculated subnet addresses, and worked with the string bitwise operators.

Source

perlop: Bitwise operators · perlop: Bitwise string operators · integer pragma · feature: bitwise

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 Perl tutorials.