目录
介绍
音频处理器通常使用定点数字表示音频信号。在开发用于连接到音频处理器的GUI的过程中,我需要一种简单的方法来向用户显示定点编号。这个简单的类是一个定点数容器,它允许将值读取或写入为定点值或double。
背景
定点数历史悠久,尤其是在浮点模块可用于CPU之前。DSP通常会使用固定数字格式,因为它们通常没有浮点模块。
音频DSP以定点数字格式表示音频信号。“1.31”的音频格式有一个数字范围或者-1...... +1。在我的特定例子中,我需要使用数字范围为-8... +8 的“4.20”格式。“4.20”格式将具有1个符号位,3个十进制位和20个小数位。
使用代码
FixedPoint类介绍如下:
/// <summary>
/// A class to convert fixed point numbers from/to doubles.
/// </summary>
public class FixedPoint
{
private readonly int _fracWidth;
private readonly int _decWidth;
private readonly int _fracMask;
private readonly int _decMask;
private readonly int _signMask;
private readonly int _fullMask;
private readonly double _minValue;
private readonly double _maxValue;
#region Properties
private readonly bool _error;
public bool Error
{
get { return _error; }
}
/// <summary>
/// Is value negative
/// </summary>
public bool IsNegative
{
get { return (ValueAsFixedPoint & _signMask) != 0; }
}
/// <summary>
/// Get decimal part of fixed number
/// </summary>
public int GetDecimal
{
get { return (ValueAsFixedPoint >> _fracWidth) & _decMask; }
}
/// <summary>
/// Get fraction part of fixed point number
/// </summary>
public int GetFraction
{
get { return ValueAsFixedPoint & _fracMask; }
}
private int _val;
/// <summary>
/// Get/Set value with fixed point number
/// </summary>
public int ValueAsFixedPoint
{
get { return _val; }
set { _val = value & _fullMask; }
}
/// <summary>
/// Get/Set value with double
/// </summary>
public double ValueAsDouble
{
get { return ConvertToDouble(_val); }
set { _val = ConvertToFixedPoint(value); }
}
#endregion
/// <summary>
/// Instantiate a fixed point number
/// </summary>
/// <param name="format">fixed point number format</param>
public FixedPoint(string format)
{
// extract pieces from the format definition
var s = format.Split(new char[] { '.' });
if (s.Length != 2) { _error = true; return; }
var b = int.TryParse(s[0], out _decWidth);
if (!b) { _error = true; return; }
b = int.TryParse(s[1], out _fracWidth);
if (!b) { _error = true; return; }
// calculate values to be used later
for (var i = 0; i < _fracWidth; ++i) _fracMask = (_fracMask << 1) + 1;
for (var i = 0; i < _decWidth - 1; ++i) _decMask = (_decMask << 1) + 1;
_signMask = 0x1 << (_decWidth + _fracWidth - 1);
for (var i = 0; i < (_fracWidth + _decWidth); ++i) _fullMask = (_fullMask << 1) + 1;
// calculate format range limits
_maxValue = ConvertToDouble(_signMask - 1);
_minValue = -(_maxValue + ConvertToDouble(1));
}
/// <summary>
/// Convert fixed point number to double
/// </summary>
/// <param name="val">fixed point number</param>
/// <returns></returns>
private double ConvertToDouble(int val)
{
if (_error) return 0;
// do positive numbers
if ((val & _signMask) == 0)
{
double x = val & ~_signMask;
for (var i = 0; i < _fracWidth; ++i)
x = x / 2;
return x;
}
// do negative numbers
else
{
var x = ((~val) & _fullMask & ~_signMask) + 1;
if (x == _signMask)
{
// do this to handle negative boundary condition
var y = ConvertToDouble(_signMask - 1);
var z = ConvertToDouble(1);
return -(y + z);
}
else
{
var y = ConvertToDouble(x);
return -y;
}
}
}
/// <summary>
/// Convert double to fixed point number
/// </summary>
/// <param name="x">value to convert</param>
/// <returns></returns>
private int ConvertToFixedPoint(double x)
{
if (_error) return 0;
// clamp value to format range
x = x > _maxValue ? _maxValue : x;
x = x <= _minValue ? _minValue : x;
// do positive doubles
if (x >= 0)
{
return ConvertToPositiveFixedPoint(x);
}
// and now for negative doubles
else
{
var zz = ConvertToPositiveFixedPoint(-x) - 1;
zz = ~zz & _fullMask;
return zz;
}
}
/// <summary>
/// Converts positive doubles to fixed point number
/// </summary>
/// <param name="x">double to convert to fixed point</param>
/// <returns>fixed point</returns>
private int ConvertToPositiveFixedPoint(double x)
{
// get decimal and fractional parts
var dec = Math.Floor(x);
var frac = x - dec;
var val = 0;
var bit = 0x1 << (_fracWidth - 1);
for (var i = 0; i < _fracWidth; ++i)
{
var testVal = val + bit;
var y = ConvertToDouble(testVal);
if (y <= frac)
val = testVal;
bit = bit >> 1;
}
return ((int)dec << _fracWidth) + val;
}
}
一个简单的测试程序如下所示:
- 首先,创建具有指定格式“new FixedPoint("4.20")” 的类的实例
- 将实例设置为测试值,在这种情况下为“fp.ValueAsDouble = 5.1234;”
- 取回定点编号“fpTestVal = fp.ValueAsFixedPoint;”
- 将其反馈回实例“fp.ValueAsFixedPoint = fpTestVal;”
- 取回值为double “convertedVal = fp.ValueAsDouble;”
- 测试值和返回值应该相同。
void Main()
{
var fp = new FixedPoint("4.20"); // set format
var testVal = fp.ValueAsDouble = 5.1234; // set test value
Console.WriteLine($"test value = {testVal:F6}");
var fpTestVal = fp.ValueAsFixedPoint; // get the converted value
// and the bits and pieces of the fixed point number
var sign = fp.IsNegative ? "-" : "+";
var dec = fp.GetDecimal;
var frac = fp.GetFraction;
var h = $"fixed point value = {sign}:{dec:X1}:{frac:X5} {fpTestVal:X6}";
Console.WriteLine(h);
// now set 'fp' to test fixed point number
fp.ValueAsFixedPoint = fpTestVal;
// and convert it back to a double, should be the same as you started with
var convertedVal = fp.ValueAsDouble;
Console.WriteLine($"converted value = {convertedVal:F6}");
}
控制台输出如下:
test value = 5.123400
fixed point value = +:5:1F972 51F972
converted value = 5.123400
此类允许任何格式的定点数,只要总位数不超过32('int' 的大小)即可。到目前为止,我看到的常见格式是1.15、1.31、4.20、5.23和9.23,但是您可以创建和使用自己的格式。