目錄
介紹
背景
使用代碼
介紹
音頻處理器通常使用定點數字表示音頻信号。在開發用于連接配接到音頻處理器的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,但是您可以建立和使用自己的格式。