天天看點

定點數轉換器介紹背景使用代碼

目錄

介紹

背景

使用代碼

介紹

音頻處理器通常使用定點數字表示音頻信号。在開發用于連接配接到音頻處理器的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;
    }
}
           

一個簡單的測試程式如下所示:

  1. 首先,建立具有指定格式“new FixedPoint("4.20")” 的類的執行個體
  2. 将執行個體設定為測試值,在這種情況下為“fp.ValueAsDouble = 5.1234;”
  3. 取回定點編号“fpTestVal = fp.ValueAsFixedPoint;”
  4. 将其回報回執行個體“fp.ValueAsFixedPoint = fpTestVal;”
  5. 取回值為double “convertedVal = fp.ValueAsDouble;”
  6. 測試值和傳回值應該相同。
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,但是您可以建立和使用自己的格式。