Acme-Bitfield
view release on metacpan or search on metacpan
CODE_OF_CONDUCT.md view on Meta::CPAN
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.1.0] - 2026-02-02
### Added
- Added `is_full()` and `is_empty()` methods for quick status checks.
- Added bitwise set operations: `union( ... )`, `intersection( ... )`, and `difference( ... )`.
## [v1.0.0] - 2026-01-29
### Changed
- It actually exists.
[Unreleased]: https://github.com/sanko/Acme-Bitfield.pm/compare/v1.1.0...HEAD
[v1.1.0]: https://github.com/sanko/Acme-Bitfield.pm/compare/v1.0.0...v1.1.0
[v1.0.0]: https://github.com/sanko/Acme-Bitfield.pm/releases/tag/v1.0.0
{
"X_No_Archive" : "Yes",
"abstract" : "Bitmask for Tracking Boolean Sets",
"author" : [
"Sanko Robinson <sanko@cpan.org>"
],
"description" : "Provides a compact way to track a large set of boolean flags.",
"dynamic_config" : 0,
"generated_by" : "App::mii v1.0.0",
"keywords" : [
"bitfield",
"bittorrent",
"interplanetary"
],
"license" : [
"artistic_2"
],
Acme::Bitfield - Bitmask for Tracking Boolean Sets
# SYNOPSIS
```perl
use Acme::Bitfield;
my $bf = Acme::Bitfield->new( size => 100 );
# Mark item 42 as present
$bf->set( 42 );
# Check if we have item 42
say 'Found it!' if $bf->get(42);
# Statistics
printf "Progress: %.2f%%\r", ($bf->count / $bf->size * 100);
# Export raw binary for network transfer
my $raw = $bf->data;
```
# DESCRIPTION
`Acme::Bitfield` provides a compact way to track a large set of big endian boolean flags. It is specifically designed
to follow the BitTorrent (BEP 03) bit-ordering convention, where the most significant bit of the first byte represents
index 0.
## Bit Ordering
\* Byte 0, Bit 0 (0x80) -> Index 0 \* Byte 0, Bit 7 (0x01) -> Index 7 \* Byte 1, Bit 0 (0x80) -> Index 8
This is the inverse of Perl's internal `vec` bit-ordering, and this module handles the necessary bit-swizzling
transparently.
# METHODS
## `get( $index )`
Returns 1 if the bit at `$index` is set, 0 otherwise.
## `set( $index )`
Sets the bit at `$index` to 1.
## `clear( $index )`
Sets the bit at `$index` to 0.
## `count( )`
Returns the total number of bits set to 1.
## `is_full( )`
Returns true if all bits are set to 1.
## `is_empty( )`
Returns true if all bits are set to 0.
## `size( )`
Returns the total capacity of the bitfield.
## `data( )`
Returns the raw binary string representation of the bitfield.
## `set_data( $string )`
Sets the raw binary representation. The input string will be truncated or padded to match the `size`, and any excess
bits in the last byte will be zeroed.
## `fill( )`
Sets all bits within the `size` to 1.
## `find_missing( )`
Returns the index of the first bit set to 0, or `undef` if all bits are set.
## `inverse( )`
Returns a new bitfield object with all bits within the `size` inverted. Bit 0 becomes 1, and 1 becomes 0.
## `union( $other )`
Returns a new bitfield object representing the bitwise OR of this bitfield and `$other`.
## `intersection( $other )`
Returns a new bitfield object representing the bitwise AND of this bitfield and `$other`.
## `difference( $other )`
Returns a new bitfield object representing the bits set in this bitfield but NOT in `$other` (bitwise AND NOT).
# AUTHOR
Sanko Robinson <sanko@cpan.org>
# COPYRIGHT
Copyright (C) 2026 by Sanko Robinson.
This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.
lib/Acme/Bitfield.pm view on Meta::CPAN
use feature 'class';
no warnings 'experimental::class';
#
class Acme::Bitfield v1.1.0 {
field $size : reader : param;
field $data : reader : param = "\0" x int( ( $size + 7 ) / 8 );
ADJUST {
$self->_clean;
}
method set_data ($val) {
$data = $val;
$self->_clean; # We can't use the :writer because we must call this
}
# Internal helper to map BitTorrent bit index to vec index
# BT: bit 0 is 0x80, bit 7 is 0x01
# vec: bit 0 is 0x01, bit 7 is 0x80
sub _map ($index) { ( $index & ~7 ) | ( 7 - ( $index & 7 ) ) }
method get ($index) {
return 0 if $index < 0 || $index >= $size;
vec $data, _map($index), 1;
}
method set ($index) {
return if $index < 0 || $index >= $size;
vec( $data, _map($index), 1 ) = 1;
}
method clear ($index) {
return if $index < 0 || $index >= $size;
vec( $data, _map($index), 1 ) = 0;
}
method count () {
lib/Acme/Bitfield.pm view on Meta::CPAN
method is_full () {
return $self->count == $size;
}
method is_empty () {
return $data =~ tr/\0//c ? 0 : 1;
}
method union ($other) {
my $new = __CLASS__->new( size => $size );
$new->set_data( $data|.$other->data );
return $new;
}
method intersection ($other) {
my $new = __CLASS__->new( size => $size );
$new->set_data( $data&.$other->data );
return $new;
}
method difference ($other) {
# Bits set in self but NOT in other
my $new = __CLASS__->new( size => $size );
$new->set_data( $data&.~.$other->data );
return $new;
}
method _clean () {
# internal method to automatically handle data truncation, padding, and bit masking
my $expected_len = int( ( $size + 7 ) / 8 );
if ( length($data) > $expected_len ) {
substr( $data, $expected_len ) = "";
}
lib/Acme/Bitfield.pm view on Meta::CPAN
$self->_clean;
}
method find_missing () {
my $index = index( unpack( 'B*', $data ), '0' );
return ( $index >= 0 && $index < $size ) ? $index : ();
}
method inverse () {
my $inverted = __CLASS__->new( size => $size );
$inverted->set_data( ~.$data );
return $inverted;
}
};
#
1;
lib/Acme/Bitfield.pod view on Meta::CPAN
Acme::Bitfield - Bitmask for Tracking Boolean Sets
=head1 SYNOPSIS
use Acme::Bitfield;
my $bf = Acme::Bitfield->new( size => 100 );
# Mark item 42 as present
$bf->set( 42 );
# Check if we have item 42
say 'Found it!' if $bf->get(42);
# Statistics
printf "Progress: %.2f%%\r", ($bf->count / $bf->size * 100);
# Export raw binary for network transfer
my $raw = $bf->data;
=head1 DESCRIPTION
C<Acme::Bitfield> provides a compact way to track a large set of big endian boolean flags. It is specifically designed
to follow the BitTorrent (BEP 03) bit-ordering convention, where the most significant bit of the first byte represents
index 0.
=head2 Bit Ordering
* Byte 0, Bit 0 (0x80) -> Index 0 * Byte 0, Bit 7 (0x01) -> Index 7 * Byte 1, Bit 0 (0x80) -> Index 8
This is the inverse of Perl's internal C<vec> bit-ordering, and this module handles the necessary bit-swizzling
transparently.
=head1 METHODS
=head2 C<get( $index )>
Returns 1 if the bit at C<$index> is set, 0 otherwise.
=head2 C<set( $index )>
Sets the bit at C<$index> to 1.
=head2 C<clear( $index )>
Sets the bit at C<$index> to 0.
=head2 C<count( )>
Returns the total number of bits set to 1.
=head2 C<is_full( )>
Returns true if all bits are set to 1.
=head2 C<is_empty( )>
Returns true if all bits are set to 0.
=head2 C<size( )>
Returns the total capacity of the bitfield.
=head2 C<data( )>
Returns the raw binary string representation of the bitfield.
=head2 C<set_data( $string )>
Sets the raw binary representation. The input string will be truncated or padded to match the C<size>, and any excess
bits in the last byte will be zeroed.
=head2 C<fill( )>
Sets all bits within the C<size> to 1.
=head2 C<find_missing( )>
Returns the index of the first bit set to 0, or C<undef> if all bits are set.
=head2 C<inverse( )>
Returns a new bitfield object with all bits within the C<size> inverted. Bit 0 becomes 1, and 1 becomes 0.
=head2 C<union( $other )>
Returns a new bitfield object representing the bitwise OR of this bitfield and C<$other>.
=head2 C<intersection( $other )>
Returns a new bitfield object representing the bitwise AND of this bitfield and C<$other>.
=head2 C<difference( $other )>
Returns a new bitfield object representing the bits set in this bitfield but NOT in C<$other> (bitwise AND NOT).
=head1 AUTHOR
Sanko Robinson E<lt>sanko@cpan.orgE<gt>
=head1 COPYRIGHT
Copyright (C) 2026 by Sanko Robinson.
This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.
use v5.40;
use Test2::V0;
use lib '../lib';
use Acme::Bitfield;
#
subtest Basics => sub {
subtest Operations => sub {
my $bf = Acme::Bitfield->new( size => 10 );
is $bf->size, 10, 'Correct size';
is $bf->count, 0, 'Initially empty';
$bf->set(0);
$bf->set(9);
ok $bf->get(0), 'Bit 0 set';
ok $bf->get(9), 'Bit 9 set';
ok !$bf->get(5), 'Bit 5 not set';
is $bf->count, 2, 'Count is 2';
$bf->clear(0);
ok !$bf->get(0), 'Bit 0 cleared';
is $bf->count, 1, 'Count is 1';
};
subtest 'Bit Ordering (BEP 03)' => sub {
my $bf = Acme::Bitfield->new( size => 8 );
$bf->set(0); # Should be 0x80 in the first byte
is unpack( 'H*', $bf->data ), '80', 'Index 0 is high bit of first byte';
$bf->clear(0);
$bf->set(7); # Should be 0x01
is unpack( 'H*', $bf->data ), '01', 'Index 7 is low bit of first byte';
};
subtest 'Fill and Find Missing' => sub {
my $bf = Acme::Bitfield->new( size => 5 );
$bf->fill();
is $bf->count, 5, 'All 5 bits set';
is $bf->find_missing(), undef, 'No missing bits';
$bf->clear(2);
is $bf->find_missing(), 2, 'Found missing bit at index 2';
};
};
subtest Inverse => sub {
subtest 'Inverse Method' => sub {
my $bf = Acme::Bitfield->new( size => 10 );
$bf->set(0);
$bf->set(5);
$bf->set(9);
is $bf->count, 3, 'Initial count is 3';
my $inv = $bf->inverse();
isa_ok $inv, ['Acme::Bitfield'], 'inverse() returns a new Acme::Bitfield';
is $inv->size, 10, 'Inverted bitfield has same size';
is $inv->count, 7, 'Inverted bitfield has count 7 (10 - 3)';
ok !$inv->get(0), 'Bit 0 is now 0';
ok $inv->get(1), 'Bit 1 is now 1';
ok !$inv->get(5), 'Bit 5 is now 0';
ok $inv->get(8), 'Bit 8 is now 1';
ok !$inv->get(9), 'Bit 9 is now 0';
$bf->fill();
my $inv = $bf->inverse();
is $inv->count, 0, 'Inverse of full is empty';
is unpack( 'H*', $inv->data ), '00', 'Data is 0x00';
};
subtest 'Excess Bits remain zero' => sub {
my $bf = Acme::Bitfield->new( size => 10 );
my $inv = $bf->inverse();
# 10 bits means 2 bytes.
# Inverted should have 10 bits set to 1.
# Bits 10-15 should remain 0.
# Byte 1: 11111111 (0xFF)
# Byte 2: 11000000 (0xC0 in BEP 03 order)
is unpack( 'H*', $inv->data ), 'ffc0', 'Excess bits are zeroed out in inverted bitfield';
};
};
subtest 'Bitwise Operations' => sub {
my $bf1 = Acme::Bitfield->new( size => 10 );
my $bf2 = Acme::Bitfield->new( size => 10 );
$bf1->set($_) for ( 0, 1, 2 );
$bf2->set($_) for ( 2, 3, 4 );
subtest 'Union' => sub {
my $union = $bf1->union($bf2);
is( $union->count, 5, 'Union count is 5' );
ok( $union->get($_), "Bit $_ set in union" ) for ( 0, 1, 2, 3, 4 );
};
subtest 'Intersection' => sub {
my $inter = $bf1->intersection($bf2);
is( $inter->count, 1, 'Intersection count is 1' );
ok( $inter->get(2), 'Bit 2 set in intersection' );
ok( !$inter->get(0), 'Bit 0 NOT set in intersection' );
};
subtest 'Difference' => sub {
my $diff = $bf1->difference($bf2);
is( $diff->count, 2, 'Difference count is 2' );
ok( $diff->get(0), 'Bit 0 set in difference' );
ok( $diff->get(1), 'Bit 1 set in difference' );
ok( !$diff->get(2), 'Bit 2 NOT set in difference' );
};
};
subtest 'Status Checks' => sub {
my $bf = Acme::Bitfield->new( size => 8 );
ok( $bf->is_empty, 'Initially empty' );
ok( !$bf->is_full, 'Not initially full' );
$bf->fill;
ok( $bf->is_full, 'Full after fill' );
ok( !$bf->is_empty, 'Not empty after fill' );
$bf->clear(0);
};
subtest 'Edge Cases' => sub {
subtest 'Zero Size' => sub {
my $bf = Acme::Bitfield->new( size => 0 );
is( $bf->data, '', 'Data is empty string' );
is( $bf->count, 0, 'Count is 0' );
ok( $bf->is_full, 'Zero size is technically full' );
};
subtest 'Mismatched Data Length' => sub {
my $bf = Acme::Bitfield->new( size => 8 );
$bf->set_data("\xFF\xFF\xFF");
is( length( $bf->data ), 1, 'Data truncated to 1 byte' );
is( $bf->count, 8, 'Count is 8' );
$bf->set_data("");
is( length( $bf->data ), 1, 'Data padded to 1 byte' );
is( ord( $bf->data ), 0, 'Padded with zeros' );
};
subtest 'Last Byte Masking' => sub {
my $bf = Acme::Bitfield->new( size => 10 );
# 10 bits = 2 bytes. Last byte should only have 2 bits.
$bf->set_data("\xFF\xFF");
is( ord( substr( $bf->data, 1, 1 ) ), 0xC0, 'Last byte masked to 0xC0' );
is( $bf->count, 10, 'Count is 10' );
};
};
#
done_testing;
( run in 0.729 second using v1.01-cache-2.11-cpan-d8267643d1d )