Perl中的位操作入门
2024-05-21 21:19:37 阿炯

Perl提供了几种处理二进制数据的方法,与C语言中的位操作符类似。
The bitwise operators &, |, and ~.
The pack and unpack functions.
The vec function.

用法示例
my $mask = 1 << 3;    # 0x0008
$value |=  $mask;    # set bit
$value &= ~$mask;    # clear bit
if ($value & $mask)    # check bit

vec is designed for use with bit vectors(Each element has the same size, which must be a power of two). It could work here as well:
vec($value, 3, 1) = 1;    # set bit
vec($value, 3, 1) = 0;    # clear bit
if (vec($value, 3, 1))    # check bit


位操作并不是一个真正的Perl特定主题,但对于刚开始编程的人来说这仍然是一个问题。

有4种位运算:'and','or','xor','inverse',在Perl中可以作为字符运算符&,|,^和~来对照使用,因为它们不会与实际的and,or和not逻辑运算符相混淆,因为前者的操作符是用于位操作运算,所以它们对数字中的单个位有效,在Perl中它们也神奇地对字符串中的所有位有效。

除了反(inverse)运算,其它还需要两个也称为操作数的参数,一个左操作数和一个右操作数。因为这些操作中的哪一个是右边还是左边都不重要,所以它们会产生相反的相同结果,而inverse只需要一个操作数。

运算符:AND(&)

这是需要知道两个项目是否都是真的时使用的运算符,位中的true和false通常分别为1和0或on和off。


因此,如果有数字234和15,在比特表示11101010和00001111中,做运算(234 & 15):将左操作数中的每个位与右操作数中相应的位进行比较,因此结果是00001010或十进制的10。

以字符为例,我们有一个'A'和'a',分别是ASCII中的01000001和01100001。如果对其进行&运算将得到01000001或'A':
ord("a") & ord("A");

运算符:OR(|)

此运算符检查右操作数或左操作数是否为true,或者它们是否都为true。因此,只有当两个操作数都为false时,这才是false。


再以上面的例子对234和15 或 11101010和00001111进行'|'运算,结果是11101111或239。以ASCII中的'A'和'a'或01000001和01100001为例,结果为01100001或'A'。

运算符:NOT(~)

反运算符只接受一个参数,并简单地反转所有位。因此,所有真值变为假,所有假值变为真。


这个操作符实际上也被称为not操作符,但大多数人将其与其他同名操作符联系在一起,!操作事实上,~是“位”非运算符。这个运算符用于将真值反转为假值,而不是实际查看特定的位。Perl也是这样做的,其他一切都会让人大吃一惊,想象一下如果!"1",按位执行后1是00110001,所以!"1"应为11001110,在iso8859-1中为“LATIN CAPITAL LETTER I WITH CIRCUMFLEX”。。。但是在perl中!"1"为0,因此为false。为了进一步混淆这一点,~也被称为(1's)补码运算符,或逐位否定运算符。

与想象的不同,~234不是00010101,而是11111111111111111111111100010101…这只是因为Perl对数字的表示比简单的8位长得多。此外,这是在我的机器和我编译的Perl版本上,在其他机器平台上它可能会更长。

运算符:XOR(^)

XOR运算符与公共语言中已知的其他运算符不同,主要是因为它是由更多运算组成的。其表示异或,在其他位运算中表示为(a&~b)|(~a&b)。


另外还有两个位的操作符:<<、>>,用于移位操作:
my $high_bit_set = 1 << 8;     # 0b1000_0000

my $second_byte  = 0xFF << 8;  # 0x00_00_FF_00


布尔运算符

布尔运算符(! not;&& and;|| or)与逐位运算符非常相似。已经看了!运算符,它将真值转换为假值,将假值转换为真值。因此,实际上就好像我们将整个表达式转换为一个位并反转该位。

&&通过计算左操作数来工作,当且仅当返回真值时,计算右操作数并返回结果。||工作原理类似,但仅在右操作数为false时计算左操作数。因此与它们的按位操作不同,操作数的排序在这里很重要:$a && $b/$a if $a is 0...

不应该将and运算符与&运算符混淆,这是因为和与&&相同。相同的适用于或与||相同的!,事实并非如此。不同之处在于,命名版本的前缀较低,这意味着当Perl查看代码时,隐式括号的放置方式不同。

部分运算会对变量的运算产生影响(而这些运算是不容易被发现的):

my ($a,$b) = (0) x 2;
my $c = $a++ && $b++;  # only $a++ is evaluated
print "a = $a, b = $b, c = $c\n";  # prints "a = 1, b = 0, c = 0"

my ($a,$b) = (0) x 2;
$c = ++$a || ++$b;  # only ++$a is evaluated
print "a = $a, b = $b\n";  # prints "a = 1, b = 0"

另外还有相关的位操作模块与函数:Bit::Vectors、Devel::Peek、Devel::Size、pack、unpack、vec函数


从C代码优化中移植到Perl下的一些优化:
[hto@yklin:~/freeoa]$ more bitopcmp1.pl
use v5.32;
use Time::HiRes qw(time);
#22-09-03
my ($a,$st)=(1234567,time());

say $a%8;
say "Div time used:".tdnum(time()-$st);
#bit and arithmetic
$st=time();
say $a&7;
say "Bca time used:".tdnum(time()-$st);

#
sub tdnum{
    my $ino=shift;
    return sprintf "%.7f",abs($ino);
}

一次结果
7
Div time used:0.0000391
7
Bca time used:0.0000060

使用位运算的结果还是比较明显。

尽管Perl基本上将开发者与计算机的物理细节隔离开来,但有时当数据以字节的形式出现时,仍然需要处理它们。或者,如果Perl的数据结构占用了太多的内存来解决所遇到的问题,可能希望将数据打包成位字符串,以避免Perl的内存损失。一旦有了这些比特,就可以用与其他语言相同的方式来处理它们。

perlop 文档中对 bitwise 操作符有说明,perlfunc 文档中包含了对内建函数vec的介绍。

参考来源
编程语言基础知识之位运算与位移动

Bit operations for beginners

Mastering Perl Chapter 16. Working with Bits


获取主机操作系统的部分运行信息,为一些计算打基础

以bytes为单位取得主机OS原生的shorts, ints, longs, and long longs长度:
$ perl -V:{short,int,long{,long}}size
shortsize='2';
intsize='4';
longsize='4';
longlongsize='8';

or programmatically via the Config module:
use Config;
print $Config{shortsize},"\n";
print $Config{intsize},"\n";
print $Config{longsize},"\n";
print $Config{longlongsize},"\n";

如果系统不支持long long类型则$Config{longlongsize}将会是undefined。

平台的端序(byteorder)
use Config;
print "$Config{byteorder}\n";

$ perl -V:byteorder

Byteorders "1234" and "12345678" are little-endian; "4321" and "87654321" are big-endian.