Main Content

执行定点算术

此示例说明如何执行基本的定点算术运算。

开始前保存所有警告的当前状态。

warnstate = warning;

加减法

将两个无符号定点数相加时,您可能需要一个进位位来正确表示结果。因此,将两个 B 位数(具有相同的定标)相加时,与使用两个操作数时相比,结果值有一个额外的位。

a = fi(0.234375,0,4,6);
c = a + a
c = 
    0.4688

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Unsigned
            WordLength: 5
        FractionLength: 6
a.bin
ans = 
'1111'
c.bin
ans = 
'11110'

使用有符号的 2 的补码数时,会出现类似的情况,因为需要符号扩展来正确表示结果。

a = fi(0.078125,1,4,6);
b = fi(-0.125,1,4,6);
c = a + b
c = 
   -0.0469

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 5
        FractionLength: 6
a.bin
ans = 
'0101'
b.bin
ans = 
'1000'
c.bin
ans = 
'11101'

如果对具有不同精度的两个数字执行加法或减法,首先需要对齐小数点才能执行运算。结果是运算结果和操作数之间存在多于一位的差异(取决于操作数的小数点相差多少位)。

a = fi(pi,1,16,13);
b = fi(0.1,1,12,14);
c = a + b
c = 
    3.2416

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 18
        FractionLength: 14

加减法的其他考虑事项

建议要使用以下模式。由于标量加法是在 for 循环中的每次迭代时执行的,因此,在每次迭代期间都会将一个位添加到 temp 中。其结果是,位增长是 Nadds,而不是 ceil(log2(Nadds))

s = rng; rng('default');
b = fi(4*rand(16,1)-2,1,32,30);
rng(s); % restore RNG state
Nadds = length(b) - 1;
temp = b(1);
for n = 1:Nadds
    temp = temp + b(n+1); % temp has 15 more bits than b
end

如果改用 sum 命令,则可控制位增长。

c = sum(b) % c has 4 more bits than b
c = 
    7.0059

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 36
        FractionLength: 30

乘法

通常,全精度乘积需要的字长等于各操作数字长之和。在此示例中,乘积 c 的字长等于 a 的字长加上 b 的字长。c 的小数长度也等于 a 的小数长度加上 b 的小数长度。

a = fi(pi,1,20);
b = fi(exp(1),1,16);
c = a*b
c = 
    8.5397

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 36
        FractionLength: 30

赋值

当您将定点值赋给预定义的变量时,则可能会涉及量化。在这种情况下,表达式的右侧通过舍入到最接近的数字(必要时还进行饱和处理)来量化,然后再对左侧赋值。

N = 10;

a = fi(2*rand(N,1)-1,1,16,15);
b = fi(2*rand(N,1)-1,1,16,15);
c = fi(zeros(N,1),1,16,14);

for n = 1:N
    c(n) = a(n).*b(n);
end

当以全精度计算乘积 a(n).*b(n) 时,则会生成字长为 32、小数长度为 30 的中间结果。然后,该结果将量化为字长为 16 位、小数长度为 14 位。然后,将量化后的值赋给元素 c(n)

显式量化结果

通常,在量化结果时最好不要舍入到最接近的数字或进行饱和处理,因为这需要额外的逻辑和计算。对左侧赋值来执行量化可能也不是理想的做法。为此,您可以使用 quantize 函数。常见的情况是反馈回路。如果未引入量化,则随着提供更多输入数据,将发生无限制位增长。

a = fi(0.1,1,16,18);
x = fi(2*rand(128,1)-1,1,16,15);
y = fi(zeros(size(x)),1,16,14);

for n = 1:length(x)
    z    = y(n);
    y(n) = x(n) - quantize(a.*z,true,16,14,'Floor','Wrap');
end

在此示例中,乘积 a.*z 以全精度计算,然后量化为 16 位的字长和 14 位的小数长度。在量化时,进行向下取整(截断)并允许在发生溢出时绕回。量化仍然在赋值时发生,因为表达式 x(n) - quantize(a.*z,...) 生成 18 位的中间结果,而 y 定义为具有 16 位。

为了消除赋值时的量化,您可以引入额外的显式量化,这样就不会使用舍入到最接近的数字或饱和逻辑值。由于左侧的结果具有与 y(n) 相同的 16 位字长和 14 位小数长度,因此不需要量化。

a = fi(0.1,1,16,18);
x = fi(2*rand(128,1)-1,1,16,15);
y = fi(zeros(size(x)),1,16,14);
T = numerictype(true,16,14);


for n = 1:length(x)
    z    = y(n);
    y(n) = quantize(x(n),T,'Floor','Wrap') - ...
           quantize(a.*z,T,'Floor','Wrap');
end

非全精度和

全精度和并非始终生成理想的结果,可能会导致生成的 C 代码复杂且低效。例如,上述示例的中间结果 x(n) - quantize(...) 具有 18 位字长。此时,可能需要将加法和减法的所有结果保留为 16 位。您可以使用 accumposaccumneg 函数将加法和减法的结果保留为 16 位。

a = fi(0.1,1,16,18);
x = fi(2*rand(128,1)-1,1,16,15);
y = fi(zeros(size(x)),1,16,14);

T = numerictype(true, 16, 14);

for n = 1:length(x)
    z    = y(n);
    y(n) = quantize(x(n),T);                 % defaults: 'Floor','Wrap'
    y(n) = accumneg(y(n),quantize(a.*z, T)); % defaults: 'Floor','Wrap'
end

模型累加器

您可以使用 accumposaccumneg 函数对累加器进行建模。accumposaccumneg 的行为分别对应于 C 语言中的 +=-= 运算符。常见示例是 FIR 滤波器,其中系数和输入数据用 16 位表示。乘法以全精度执行,产生 32 位和一个具有 8 个保护位的累加器。总共使用 40 位来实现多达 256 次累加,而不会发生溢出。

b = fi(1/256*[1:128,128:-1:1],1,16); % Filter coefficients
x = fi(2*rand(300,1)-1,1,16,15);     % Input data
z = fi(zeros(256,1),1,16,15);        % Used to store the states
y = fi(zeros(size(x)),1,40,31);      % Initialize Output data

for n = 1:length(x)
    acc = fi(0,1,40,31); % Reset accumulator
    z(1) = x(n);        % Load input sample
    for k = 1:length(b)
        acc = accumpos(acc,b(k).*z(k)); % Multiply and accumulate
    end
    z(2:end) = z(1:end-1); % Update states
    y(n) = acc;            % Assign output
end

矩阵算术

为了简化语法和缩短仿真时间,您可以使用矩阵算术。对于 FIR 滤波器示例,您可以用内积代替内环。

z = fi(zeros(256,1),1,16,15); % Used to store the states
y = fi(zeros(size(x)),1,40,31);

for n = 1:length(x)
    z(1) = x(n);
    y(n) = b*z;
    z(2:end) = z(1:end-1);
end

内积 b*z 以全精度执行。由于这是矩阵运算,所涉及的乘法运算和将所得乘积相加的运算都会导致位增长。因此,位增长取决于操作数的长度。在此示例中,bz 的长度为 256,因此加法运算导致 8 位增长。内积产生 32 + 8 = 40 位,小数长度为 31 位。在赋值 y(n) = b*z 中不会发生量化,因为 y 已初始化为这种格式。

如果您必须执行超过 256 个系数的内积,位增长将比乘积所需的 32 位多 8 位以上。如果您只有一个 40 位累加器,则可以通过引入量化器来对行为建模,如 y(n) = quantize(Q,b*z) 中所示,也可以使用 accumpos 函数。

计数器的建模

您可以使用 accumpos 对一个简单计数器进行建模,该计数器在达到其最大值后绕回。例如,您可以按如下所示对 3 位计数器建模。

c = fi(0,0,3,0);
Ncounts = 20; % Number of times to count
for n = 1:Ncounts
    c = accumpos(c,1);
end

由于该 3 位计数器在达到 7 后绕回到 0,因此,计数器的最终值为 mod(20,8) = 4

其他内置数据类型的算术运算

在 C 语言中,整数数据类型和双精度数据类型之间的运算结果会提升为双精度类型。但是,在 MATLAB® 中,内置整数数据类型和双精度数据类型之间的运算结果是整数。在这方面,fi 对象的行为与 MATLAB 中的内置整数数据类型相似。fidouble 之间的运算结果是 fi

fi * double

fidouble 之间进行乘法运算时,double 会转换为 fi,且具有与原 fi 相同的字长和符号性以及最佳精度的小数长度。该运算的结果是 fi

a = fi(pi);
b = 0.5 * a
b = 
    1.5708

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 32
        FractionLength: 28

fi + doublefi - double

fidouble 之间进行加法或减法运算时,双精度会转换为与原 fi 具有相同 numerictypefi。该运算的结果是 fi

a = fi(pi);
b = a + 1
b = 
    4.1416

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 17
        FractionLength: 13

fi * int8

在内置整数数据类型 [u]int[8,16,32,64] 之一与 fi 之间进行算术运算时,保留整数的字长和符号性。该运算的结果是 fi

a = fi(pi);
b = int8(2) * a
b = 
    6.2832

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Signed
            WordLength: 24
        FractionLength: 13

还原警告状态。

warning(warnstate);
%#ok<*NASGU,*NOPTS>

另请参阅

|