nextpow2 function

本文详细解析了Nextpow2函数的实现原理,介绍了如何通过位操作实现ceil(log2(x)),并对比了Matlab中Nextpow2函数的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

y = nextpow2(x);
则2 y为大于等于x的最小的二的整数次幂的数字。这样说起来似乎有些拗口,举个例子,如果x等于100,则y=7,因为2 7==128,而128是所有大于100的,二的整数次幂数字中最小的一个。
      对于特殊的数值,下面一一解释:
负数:  如果x<0,则nextpow2(x)等效于nextpow2(abs(x));
无穷大:如果x==inf或x==-inf,nextpow2(x)==inf
NaN:   如果x==NaN,nextpow2(x)==NaN
    如果叫我们来实现nextpow2,且不考虑上面的三类数值,那么首先想到的方法大概就是:
function y = nextpow2(x)
y = ceil(log2(x));
但是有个问题,没有考虑x为0的情况,所以稍微精炼一下:
function y = nextpow2(x)
if (x == 0)
  y = 0;
else
  y = ceil(log2(x));
end
这样应该很好了吧!
      但是实际上这并不是我首选的方法,也不是Matlab的nextpow2所选择的方法。虽然很直观,很数学。由于我曾经在FPGA上做过一些简单的浮点算法,所以对于IEEE754标准有一点了解,并写了系列笔记 《探索浮点运算》。在 《计算对数函数ln》中特别提到过对数的计算。通过对于754浮点数的研究,我渐渐明白,像ceil(log2(x))这样的运算,在支持754浮点类型的机器上,完全不需要使用任何算术运算,即可实现。具体的细节就不做细致的说明了,这些在 《探索浮点运算》中的各篇笔记中已经反复说明过了。现在我演示一下如何在Python中完全利用位操作实现ceil(log2(x)):
import ctypes as ct

# 2011.03.29 PM 08:49
# ActivePython 2.6.6.15
# Win7 64
# xialulee

class FloatBits(ct.Structure):
    _fields_ [
        ('M'ct.c_uint, 23),
        ('E'ct.c_uint, 8),
        ('S'ct.c_uint, 1)
    ]

class Float(ct.Union):
    _anonymous_ ('bits')
    _fields_ [
        ('value'  ct.c_float),
        ('bits'   FloatBits)
    ]

def ceillog2(x):
    if 0:
        -x
    if == 0:
        return 0
    Float()
    d.value x
    if d.M == 0:
        return d.E 127
    return d.E 127 1

试试:
>>> ceillog2(100)
7L
(2的7次方等于128)
>>> ceillog2(4)
2L
(2的2次方等于4)
>>> ceillog2(1.5)
1L
(2的1次方等于2)
>>> ceillog2(0.1)
-3L
(2的-3次方等于0.125)
从上面的代码可以看出,完全使用操作位域的手法,即可实现ceil(log2(x))的操作,完全不需要任何算术运算,迭代算法什么的,有效地利用了机器的能力。
      现在再看看Matlab的nextpow2是如何实现的。nextpow2中第一行代码就使用了log2函数。(我不清楚Matlab的代码遵循什么协议,所以这里就不直接贴代码了)。但是这里的log2并不是普通的log2,而是log2函数的一种特殊形式:两个返回值的log2函数。根据文档,当log2函数具有两个返回值时:
[F,E] = log2(X)

X = F.*2.^E
其中E是整数,而0.5 <= abs(F) < 1
文档中同时提到,以这种形式使用log2时,可以联想C库中的frexp,这是用来拆分浮点数尾数和指数的函数。所以Matlab的nextpow2和我们上面的Python代码一样,并没有计算以2为底的对数函数的数值,而仅仅是将输入数值的尾数和浮点数进行了拆分。但并不是直接拆分。754规定的浮点数的尾数部分应该是大于等于1的(针对普通数值,不包括0,inf,nan这样的特殊数值),而log2函数返回的两个部分中,尾数部分则是在区间[0.5, 1)中。相对于是把754浮点数的尾数部分除以2,并将指数部分加1。
      现在将log2得到的整数的指数部分返回,那么nextpow2就完成任务了吧?其实不然。log2得到的尾数部分为0.5,则说明该数值754形式的尾数部分为2×0.5==1,尾数的小数部分为0,该数本身就是2的整数次幂,则此时应该返回log2得到的指数部分的数值减去1。例如:
>> [F, E] = log2(128)

F =

      0.5000


E =

        8
      接下来,就是处理inf和NaN的代码,没有什么好说的了。
function DownsampleAudio_GUI_10s % 大窗口,时域波形显示前10秒,幅度自适应 close all; fig = figure('Name','音频下采样与抗混叠实验','NumberTitle','off',... 'Color',[0.96 0.96 0.98],'Position',[100 60 1200 900],'Resize','off'); % 顶部标题 panel_top = uipanel(fig,'Position',[0,0.935,1,0.065],'BackgroundColor',[0.22 0.37 0.62],'BorderType','none'); uicontrol(panel_top,'Style','text','String','音频下采样与抗混叠实验',... 'FontSize',25,'FontWeight','bold','BackgroundColor',[0.22 0.37 0.62],... 'ForegroundColor','w','Units','normalized','Position',[0.02 0.07 0.96 0.87]); % 第二排控件区 panel_ctl = uipanel(fig,'Position',[0,0.87,1,0.07],'BackgroundColor',[0.96 0.96 0.98],'BorderType','none'); uicontrol(panel_ctl,'Style','pushbutton','String','加载音频',... 'FontSize',14,'Position',[45 9 120 38],'BackgroundColor',[0.2 0.6 0.9],... 'ForegroundColor','w','Callback',@onLoad); uicontrol(panel_ctl,'Style','text','String','下采样因子:',... 'FontSize',14,'Position',[185 18 110 24],'BackgroundColor',[0.96 0.96 0.98],'HorizontalAlignment','left'); popup_D = uicontrol(panel_ctl,'Style','popupmenu','String',{'1/2','1/3','1/4','1/8'},... 'FontSize',14,'Position',[295 14 95 32],'Callback',@onD); cb_filter = uicontrol(panel_ctl,'Style','checkbox','String','使用抗混叠滤波',... 'FontSize',14,'Position',[420 15 170 30],'Value',1,'BackgroundColor',[0.96 0.96 0.98],'Callback',@onFilter); uicontrol(panel_ctl,'Style','pushbutton','String','播放原始音频',... 'FontSize',14,'Position',[615 9 140 38],'BackgroundColor',[0.3 0.7 0.4],... 'ForegroundColor','w','Callback',@onPlayRaw); uicontrol(panel_ctl,'Style','pushbutton','String','播放下采样音频',... 'FontSize',14,'Position',[775 9 170 38],'BackgroundColor',[0.8 0.3 0.3],... 'ForegroundColor','w','Callback',@onPlayDown); uicontrol(panel_ctl,'Style','pushbutton','String','分析并画图',... 'FontSize',14,'Position',[965 9 120 38],'BackgroundColor',[0.5 0.4 0.8],... 'ForegroundColor','w','Callback',@onPlot); uicontrol(panel_ctl,'Style','pushbutton','String','误差对比',... 'FontSize',14,'Position',[1100 9 80 38],'BackgroundColor',[0.23 0.5 0.73],... 'ForegroundColor','w','Callback',@onError); % 状态栏独立一行 status = uicontrol(fig,'Style','text','String','状态: 等待加载音频...',... 'FontSize',14,'Position',[45 825 1100 32],'BackgroundColor',[0.94 0.94 0.99],'HorizontalAlignment','left'); % 大号 axes 区域 ax1 = axes('Parent',fig,'Units','pixels','Position',[100 420 470 360],'Box','on'); title(ax1,'时域波形','FontSize',17,'FontWeight','bold'); xlabel(ax1,'时间 (s)','FontSize',14); ylabel(ax1,'幅度','FontSize',14); grid(ax1,'on'); ax2 = axes('Parent',fig,'Units','pixels','Position',[680 420 470 360],'Box','on'); title(ax2,'信号频谱','FontSize',17,'FontWeight','bold'); xlabel(ax2,'频率 (Hz)','FontSize',14); ylabel(ax2,'幅度 (dB)','FontSize',14); grid(ax2,'on'); % 状态传递 data = struct('y',[],'fs',[],'yname','',... 'factors',[2 3 4 8],'curD',4,'doFilter',1); guidata(fig, struct('data',data, ... 'popup_D',popup_D, 'cb_filter',cb_filter, ... 'status',status, 'ax1',ax1,'ax2',ax2)); %% 回调区 function onLoad(~,~) s = guidata(fig); [file, path] = uigetfile({'*.wav'},'选择WAV音频'); if isequal(file,0) set(s.status,'String','状态: 取消加载'); return; end [y, fs] = audioread(fullfile(path, file)); if size(y,2)==2, y=mean(y,2); end s.data.y = y; s.data.fs = fs; s.data.yname = file; set(s.status,'String',sprintf('状态: 已加载 %s (%.1fkHz)',file,fs/1000)); guidata(fig, s); showSignal(y, fs, s.ax1, s.ax2, '原始信号'); end function onD(~,~) s = guidata(fig); s.data.curD = s.data.factors(get(s.popup_D,'Value')); guidata(fig, s); end function onFilter(src,~) s = guidata(fig); s.data.doFilter = get(src,'Value'); guidata(fig, s); end function onPlayRaw(~,~) s = guidata(fig); if isempty(s.data.y), warndlg('请先加载音频!'); return; end sound(s.data.y, s.data.fs); set(s.status,'String','状态: 正在播放原始音频...'); end function onPlayDown(~,~) s = guidata(fig); if isempty(s.data.y), warndlg('请先加载音频!'); return; end D = s.data.curD; y = s.data.y; fs = s.data.fs; if s.data.doFilter cutoff = fs/(2*D)*0.9; b = fir1(128, cutoff/(fs/2)); y = filtfilt(b,1,y); end y_down = y(1:D:end); sound(y_down, fs/D); if s.data.doFilter set(s.status,'String',sprintf('状态: 播放滤波+下采样(1/%d)',D)); else set(s.status,'String',sprintf('状态: 播放无滤波下采样(1/%d)',D)); end end function onPlot(~,~) s = guidata(fig); if isempty(s.data.y), warndlg('请先加载音频!'); return; end D = s.data.curD; y = s.data.y; fs = s.data.fs; tagstr = sprintf('下采样 1/%d', D); if s.data.doFilter cutoff = fs/(2*D)*0.9; b = fir1(128, cutoff/(fs/2)); y = filtfilt(b,1,y); tagstr=['滤波+' tagstr]; end y_down = y(1:D:end); showSignal(y_down, fs/D, s.ax1, s.ax2, tagstr); set(s.status,'String',sprintf('状态: 已分析 %s', tagstr)); end function onError(~,~) s = guidata(fig); if isempty(s.data.y), warndlg('请先加载音频!'); return; end y = s.data.y; fs = s.data.fs; factors = s.data.factors; alias_err = zeros(1, length(factors)); residue_err = zeros(1, length(factors)); N_fft = 2^nextpow2(min(length(y),fs*10)); f = fs*(0:N_fft/2)/N_fft; P1 = abs(fft(y,N_fft)/length(y)); P1 = P1(1:N_fft/2+1); P1(2:end-1)=2*P1(2:end-1); total = sum(P1.^2); for k=1:length(factors) D = factors(k); fs_down=fs/D; idx_alias = find(f > fs_down/2 & f <= fs/2); alias_err(k) = sum(P1(idx_alias).^2)/total*100; cutoff = fs_down/2*0.9; b = fir1(128, cutoff/(fs/2)); y_filt = filtfilt(b,1,y); P1f = abs(fft(y_filt,N_fft)/length(y_filt)); P1f = P1f(1:N_fft/2+1); P1f(2:end-1)=2*P1f(2:end-1); idx_res = find(f > cutoff & f <= fs/2); residue_err(k) = sum(P1f(idx_res).^2)/total*100; end figure('Color',[0.97 0.97 0.98],'Position',[350 120 530 330]); bar(factors, [alias_err(:), residue_err(:)], 'grouped'); set(gca,'XTickLabel',{'1/2','1/3','1/4','1/8'},'FontSize',15); ylabel('高频能量丢失 (%)'); xlabel('下采样因子'); legend({'混叠误差(无滤波)','残留误差(有滤波)'},'Location','northwest'); title('下采样高频混叠/残留误差对比','FontSize',15); grid on; set(s.status,'String','状态: 误差对比完成'); end % ==== 只显示前10秒并自动缩放幅度 ==== function showSignal(y, fs, ax1, ax2, tagstr) N_show = min(length(y), round(fs*10)); t = (0:N_show-1)/fs; % 取信号正幅度包络 env = envelope(y(1:N_show)); cla(ax1); plot(ax1, t, env, 'b','LineWidth',1.2); title(ax1, [tagstr ' 时域包络'],'FontSize',16,'FontWeight','bold'); xlabel(ax1, '时间 (s)','FontSize',13,'VerticalAlignment','top'); ylabel(ax1, '幅度','FontSize',13,'VerticalAlignment','bottom'); xlim(ax1, [0 10]); ylim(ax1, [0 1.1*max(env)]); grid(ax1, 'on'); % 频谱部分 N_fft = 2^nextpow2(N_show); Y = fft(y(1:N_show), N_fft); f = fs*(0:N_fft/2)/N_fft; P = abs(Y(1:N_fft/2+1))/N_show; cla(ax2); plot(ax2, f, 20*log10(P+eps), 'b','LineWidth',1.2); title(ax2, [tagstr ' 信号频谱'],'FontSize',16,'FontWeight','bold'); xlabel(ax2, '频率 (Hz)','FontSize',13,'VerticalAlignment','top'); ylabel(ax2, '幅度 (dB)','FontSize',13,'VerticalAlignment','bottom'); xlim(ax2, [0 min(5000,fs/2)]); ylim(ax2, [-120 0]); grid(ax2, 'on'); end end 修改上述代码,生成下采样音频(选取一段效果明显的,一般取1/8下采样频率)、经过抗混叠滤波后的下采样音频(与提交的下采样音频保持同一下采样频率)
最新发布
07-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值