lin2db & db2lin
lin2db
计算方式
$$ y = 20 \times \log_{10}x $$查表代码
static float log2lut_base_float[8] = { 0, 0.169925001442312f, 0.321928094887362f, 0.459431618637297f, 0.584962500721156f, 0.700439718141092f, 0.807354922057604f, 0.906890595608519f };
static float log2lut_frac_float[8] = { 0.169925001442312f, 0.152003093445050f, 0.137503523749935f, 0.125530882083859f, 0.115477217419936f, 0.106915203916512f, 0.0995356735509144f, 0.0931094043914815f };
inline float lin2dbLut(float x) {
//10*log10(x) 3.0103*log2(x)
float M, result;
int xi = *(int*)&x;//interpret float as raw int number
int E = (xi >> 23) - 127;//float point, the 30~23bit is exponent
xi &= 0x007FFFFF;//extract the mantissa bit 22~0bit
xi |= ((int)127 << 23);//set the exponent bit to 127, which actuall is 0
M = *(float*)&xi - 1;//interpret the mantissa as float
M = M * 8;
result = (E + log2lut_base_float[(int)M] + (M - (int)M) * log2lut_frac_float[(int)M]) * 6.020599913279624;
return result;
// return (log2lut(x)*6.020599913279624);
}
快速计算
union FloatInt { float f; int l; } p; // 共用同一内存
p.f = fabsf(buffer[i]);
const int log_2 = ((p.l >> 23) & 255) - 128; // 提取指数部分,近似log2
p.l &= ~(255 << 23); // 做掩码,清除原指数位
p.l += 127 << 23; // 将指数设为127(实际为0),将值转化为介于 1.0 和 2.0 之间的浮点数
p.f = ((-1.0f / 3.0f) * p.f + 2.0f) * p.f - 2.0f / 3.0f; // 近似log2(底数)
y = (p.f + log_2) * 6.020599913279624f;
1. 浮点数的内部结构
大多数现代计算机都遵循 IEEE 754 标准来存储浮点数。一个标准的单精度浮点数(float,通常占用32位)被分成三个部分:
- 符号位 (Sign Bit):1位,表示正负(0为正,1为负)。
- 指数位 (Exponent Bits):8位,表示数字的量级,类似于科学计数法中的 10^x 里的 x。为了能表示正负指数,它采用偏移表示法 (biased representation),即实际指数值需要减去一个固定偏移量(对于单精度浮点数是127)。
- 尾数位 (Mantissa/Fraction Bits):23位,表示数字的精确值。在标准化的浮点数中,尾数总是被假定为 1.xxxx… 的形式,其中开头的 1 是隐藏的。 所以,一个浮点数的值可以近似表示为: $$ FloatValue=\text{符号}\times\text{尾数}\times2^{\text{实际指数}} $$
2. union(联合体)的妙用
union FloatInt { float f; int l; } p;
这里定义了一个名为 FloatInt 的联合体。联合体是一种特殊的数据结构,它允许不同的数据类型共享同一块内存空间。
float f;:当你通过p.f访问时,这块内存被当作一个浮点数来解释。int l;:当你通过p.l访问时,同样的这块内存被当作一个32位整数来解释。 这个特性是实现快速对数计算的关键!它让我们无需复杂的类型转换,就能直接把浮点数的位模式当作整数来操作。
3. 计算流程详解
假设我们要计算 buffer[i] 的分贝值,即 \(20 \times \log_{10}(\text{buffer}[i])\)。
p.f = fabsf(buffer[i]);
首先,我们把 buffer[i] 的绝对值赋给 p.f。这是因为对数函数只对正数有定义,而且分贝通常也基于幅度的绝对值。现在,p.f 存储了输入值的绝对值,而 p.l 则存储了这个浮点数的原始位模式(以整数形式)。
3.1 提取指数部分(粗略的 log_2)
const int log_2 = ((p.l >> 23) & 255) - 128;
这行代码是为了提取浮点数的实际指数。
p.l >> 23:将p.l(包含了浮点数所有位的整数)向右移动23位。由于指数位在第23到30位,这样操作后,8位指数就移动到了最低的8位。& 255:255的二进制是11111111。与它进行位与操作,可以屏蔽掉所有高于8位的其他位,只留下原来的8位指数值。- 128:根据 IEEE 754 标准,8位指数的偏移量是127。这里减去128而不是127,可能是为了后续的计算调整,或者这个常数本身就是为了一个特定的近似公式而微调的。这个结果log_2基本上就是原始数字以 2 为底的指数(幂次),即如果原数是 \(X \approx Y \times 2^Z\),那么log_2就是 Z。
这一步得到了一个整数 log_2,它代表了原始数中 2 的幂次,是原始数以 2 为底对数的一个粗略估计。
3.2 标准化尾数(将数字转换到 [1.0,2.0) 范围)
p.l &= ~(255 << 23); // 清除原始指数位
p.l += 127 << 23; // 设置指数位为 127 (即实际指数为 0)
这两行代码的目的是将 p.f(现在的值是原始数的绝对值)标准化到一个非常特定的范围:[1.0,2.0)。
255 << 23:创建一个掩码,它的第23到30位(指数位)是1,其余是0。~ (255 << 23):对掩码取反,使得指数位变成0,而其他位是1。p.l &= ...:将p.l与这个反转掩码进行位与操作,这样就清除了p.l中原来的指数位,只保留了符号位和尾数位。127 << 23:创建一个新的位模式,表示指数为127(实际指数为 127−127=0)。p.l += ...:将这个新的指数位模式加回到p.l。
经过这两步操作,p.f 现在的值被强制转换为一个介于 1.0 和 2.0 之间的浮点数(即 \(x^{\prime}\)),而它的尾数部分保持不变。原始数字 X 可以被表示为 \(X = X^{\prime} \times 2^{\log_2}\),其中 \(X^{\prime} \in [1.0, 2.0)\)。
3.3 多项式近似 \(\log_2(x^{\prime})\)
在多项式计算之前,我们已经将原始数字 x 分解成了以下形式:
$$ x = x^{\prime} \times 2^{\log_2} $$其中:
- \(x^{\prime}\) 是原始数字的标准化尾数,它的值范围在 [1.0,2.0) 之间。
- \(\log_2\) 是从原始浮点数的指数位提取的整数,代表了 2 的幂次。
现在,代码执行了如下的多项式计算:
$$
p.f = \left(-\frac{1.0f}{3.0f}\right) \times p.f^2 + 2.0f \times p.f - \frac{2.0f}{3.0f}
$$
这里的
p.f在等式右侧代表了 \(x^{\prime}\)。因此,这个多项式是一个二次多项式 \(P(x^{\prime})\),它的形式是 \(Ax^{\prime 2} + Bx^{\prime} + C\),其中 \(A=-1/3\),\(B=2\),\(C=-2/3\)。
那么,为什么是这个特定的多项式,以及它近似的是什么呢? 这个多项式是经过精心设计的,用来近似计算 \(\log_2(x^{\prime})\) 的值。也就是说:
$$ P(x^{\prime}) \approx \log_2(x^{\prime}) $$在 [1.0,2.0) 这样一个小的区间内,使用低阶多项式来近似一个复杂的函数(如对数函数)是一种常见的优化技巧。这个多项式的系数是通过数学方法(例如,最小二乘法拟合或泰勒展开)计算出来的,以确保在指定区间内提供足够高的近似精度。
因此,多项式计算结束后,变量 p.f 的值就近似等于 \(\log_2(x^{\prime})\)。
3.4 最终的分贝值计算
我们的最终目标是计算 \(20 \times \log_{10}(x)\)。我们已经将 x 分解成了 \(x^{\prime} \times 2^{\log_2}\)。现在,我们可以利用对数的性质来推导最终的公式。
第一步:将 \(\log_{10}(x)\) 转换为以 2 为底的对数 根据对数换底公式 \(\log_b(A) = \frac{\log_c(A)}{\log_c(b)}\),我们可以将 \(\log_{10}(x)\) 转换为以 2 为底的对数:
$$ \log_{10}(x) = \frac{\log_2(x)}{\log_2(10)} $$第二步:展开 \(\log_2(x)\) 我们将 x 替换为 \(x^{\prime} \times 2^{\log_2}\):
$$ \log_2(x) = \log_2(x^{\prime} \times 2^{\log_2}) $$利用对数乘法法则 \(\log_b(A \times B) = \log_b(A) + \log_b(B)\):
$$ \log_2(x) = \log_2(x^{\prime}) + \log_2(2^{\log_2}) $$再利用对数幂法则 \(\log_b(A^C) = C \times \log_b(A)\):
$$ \log_2(x) = \log_2(x^{\prime}) + \log_2 \times \log_2(2) $$由于 \(\log_2(2) = 1\),所以:
$$ \log_2(x) = \log_2(x^{\prime}) + \log_2 $$第三步:组合并计算最终的分贝值
现在,我们将这个结果代回到第一步的公式中,并乘以 20:
$$ 20 \times \log_{10}(x) = 20 \times \frac{\log_2(x^{\prime}) + \log_2}{\log_2(10)} $$这与代码中的最后一行完美匹配:
y=(p.f+log_2)*6.020599913279624f;
其中:
p.f近似等于 \(\log_2(x^{\prime})\)。log_2是从浮点数指数部分提取的整数。6.020599913279624f这个常数就是 \(\log_2(10) \times 20\) 的精确值。
db2lin
计算方式
$$ y = 10^{0.05x} $$计算代码
// 较快
// y = e ^ {ln10 * 0.05 * x}
y = expf(x * 0.115129255f);
查表代码
static float pow2lutbase[8] = { 1,1.09050773266526,1.18920711500272,1.29683955465101,1.41421356237310,1.54221082540794,1.68179283050743,1.83400808640934 };
static float pow2lutfrac[8] = { 0.0905077326652577,0.0986993823374633,0.107632439648289,0.117374007722086,0.127997263034846,0.139582005099488,0.152215255901913,0.165991913590658 };
inline float db2linLut(float x) {
if (x<0)
if (x>-1e-4f)
return 1.0f;
else {
float f, fn, in;
int i;
x = x*0.166096404744368f;
f = x - (int)x + 1;
i = x - f;
// return ((0.9467+0.9921*f)/(1<<(-i))); //linear approximation
// return ((1.004 + 0.6494*f + 0.3427*f*f)/(1<<(-i)));//quadratic approximation
in = f * 8;
fn = in - (int)in;
return ((pow2lutbase[(int)in] + fn*pow2lutfrac[(int)in]) / (1 << (-i)));//8bit lut method // 移位在有的编译器中有问题
}
else
if (x<1e-4f)
return 1.0f;
else {
float f, fn, in;
int i;
x = x*0.166096404744368f;
f = x - (int)x;
i = x - f;
// return ((1<<i)*(0.9467+0.9921*f));//linear approximation
// return ((1<<i)*(1.004 + 0.6494*f + 0.3427*f*f));//quadratic approximation
in = f * 8;
fn = in - (int)in;
return ((pow2lutbase[(int)in] + fn*pow2lutfrac[(int)in])*(1 << i));//8bit lut method
}
}
float lin2db(float x) {
union FloatInt { float f; int l; } p; // 共用同一内存
p.f = fabsf(x);
const int log_2 = ((p.l >> 23) & 255) - 128; // 提取指数部分,近似log2
p.l &= ~(255 << 23); // 做掩码,清除原指数位
p.l += 127 << 23; // 将指数设为127(实际为0),将值转化为介于 1.0 和 2.0 之间的浮点数
p.f = ((-1.0f / 3.0f) * p.f + 2.0f) * p.f - 2.0f / 3.0f; // 近似log2(底数)
return (p.f + log_2) * 6.020599913279624f;
}