fxpmath

repository·develop·Indexed 41 days ago

https://github.com/francof2a/fxpmath

A Python library for fractional fixed-point (base 2) arithmetic and binary manipulation with NumPy compatibility. It supports arbitrary word and fractional sizes, signed/unsigned representations, and various rounding methods. The library provides the Fxp class for fixed-point variables and a comprehensive config system to control operation methods, sizing, and NumPy array output types.

Tokens
17.8K
Snippets
72
Records
90
Agent score
49%

What's inside fxpmath

  1. Use scaling and bias to represent large ranges

    develop

    The Fxp class supports a linear transformation via scale and bias. This allows you to represent a wide range of numbers using fewer bits by 'scaling down' the raw fractional value during input and 'scaling up' during output/operations.

    Formula: Stored Value = (Actual Value - bias) / scale (conceptually applied during input/output).

    This is useful when your data resides in a specific range (e.g., [10000, 12000]) and you want to avoid using massive bit-widths to represent the offset.

    # Representing numbers in range [10000, 12000] with 0.5 precision
    # using only 12 bits
    x = Fxp(10128.5, signed=False, n_word=12, scale=1, bias=10000)
    
    # Check the scaling configuration
    x.info(3)
  2. Understand the Fxp config class

    develop

    The config class is a parameter set that determines the behavior of an Fxp object during various processes (arithmetic, sizing, array operations, etc.). You can access and modify these settings directly on an Fxp instance.

    To view the current configuration of an Fxp object, use the .config.print() method.

    x = Fxp(None, True, 16, 4)
    x.config.print()
  3. Perform arithmetic with Fxp objects

    develop

    Fxp supports standard arithmetic operations with constants and other Fxp objects.

    Arithmetic with Constants

    Operations like +, -, *, /, //, %, and ** return a new Fxp object.

    • The precision of the result is determined by x.config.const_op_sizing.
    • The constant is converted to an Fxp using x.config.op_input_size (default is 'best').

    Arithmetic with Fxp Operands

    When using two Fxp objects, the result's size depends on x.config.op_sizing (default is 'optimal').

    Controlling Result Precision

    To ensure a math operation result fits into a specific pre-defined format, use the .equal() method:

    y = Fxp(None, signed=True, n_word=32, n_frac=16)
    y.equal(x1 * x3 - 3 * x2)
    x1 = Fxp(-7.25, signed=True, n_word=16, n_frac=8)
    x2 = Fxp(1.5, signed=True, n_word=16, n_frac=8)
    
    # Result is a new Fxp object
    y = x1 + x2
    
    # Using a specific target variable for the result
    y_target = Fxp(None, signed=True, n_word=32, n_frac=16)
    y_target.equal(x1 * x2)
  4. Configure rounding methods

    develop

    When a value cannot be represented exactly due to bit constraints, Fxp applies a rounding method. You can specify the rounding mode during instantiation or update it later.

    Supported modes:

    • trunc (default): Round toward zero (like numpy.trunc).
    • around: Round to nearest; ties go to even.
    • floor: Round toward $-\infty$.
    • ceil: Round toward $+\infty$.
    • fix: Round toward zero.
    • nearest_posinf: Round to nearest; ties go toward $+\infty$.
    • nearest_neginf: Round to nearest; ties go toward $-\infty$.
    • nearest_zero: Round to nearest; ties go toward zero.
    • nearest_away: Round to nearest; ties go away from zero.
    • bit_trunc: Bit-level truncation (equivalent to floor at the quantization stage).

    To check the precision of an object, use .precision.

    # at instantiation
    x = Fxp(3.25, True, 16, 8, rounding='floor')
    
    # after instantiation
    x.rounding = 'trunc'
    x.rounding = 'around'
    x.rounding = 'floor'
    x.rounding = 'ceil'
    x.rounding = 'fix'
    x.rounding = 'nearest_posinf'
    x.rounding = 'nearest_neginf'
    x.rounding = 'nearest_zero'
    x.rounding = 'nearest_away'
    x.rounding = 'bit_trunc'
    
    print(x.precision)
  5. Understand the generalized sizing model in Fxp

    develop

    In fxpmath, the Fxp type decou decouples storage width (n_word) from scaling (n_frac). The value is interpreted using the formula:

    value = integer * 2**(-n_frac)

    Where integer is the signed or unsigned integer stored in n_word bits. This decoupling allows for unusual sizing configurations where the binary point can exist outside the range of the stored word bits.

    Sizing Relations

    The number of integer bits (n_int) is derived automatically:

    • Signed: n_int = n_word - n_frac - 1
    • Unsigned: n_int = n_word - n_frac
    # Core interpretation formula:
    # value = integer * 2**(-n_frac)
  6. Use scaling and bias for data representation

    develop

    You can represent a wide range of numbers using fewer bits by applying a linear transformation: scaled_value = (raw_value * scale) + bias.

    This is useful when your data exists in a specific range (e.g., [10000, 12000]) and you want to map it to a smaller bit-width. The scale and bias parameters are provided during instantiation.

    # Represents numbers in range [10000, 12000] with 0.5 precision using 12 bits
    x = Fxp(10128.5, signed=False, n_word=12, scale=1, bias=10000)
    
    x.info(3)
  7. How to use `out_like` for template-based output control

    develop

    out_like is an fxpmath extension and is not a standard NumPy keyword. To ensure it is recognized, avoid using top-level NumPy functions, as they may reject out_like as an unknown keyword before fxpmath can dispatch it.

    Recommended usage:

    • Use direct fxpmath APIs (e.g., fxpmath.functions.*).
    • Use Fxp object methods that forward into fxpmath-managed operations.
  8. How `out` behavior works in NumPy dispatch

    develop

    When using fxpmath through NumPy dispatch paths, the out parameter follows strict typing rules to avoid ambiguous casting:

    • Explicit out: If you provide an out argument, it must be an Fxp object.
    • Non-Fxp out: Passing any object that is not an Fxp instance to the out parameter will raise a TypeError.
    • Omission: Omitting the out parameter is treated the same as providing an out argument that defaults to standard behavior.
  9. Configure overflow and underflow behavior

    develop

    When a value exceeds the upper or lower limits of the Fxp format, the overflow behavior determines how the value is stored.

    Available modes:

    • saturate (default): Clips the value to the nearest limit (e.g., if upper limit is 15.75 and you try to store 18.0, it stores 15.75).
    • wrap: Wraps the value around the valid range (e.g., if upper limit is 15.75 and you try to store 18.0, it wraps to the lower limit range).

    You can set this during instantiation or by modifying the .overflow attribute.

    Note: Fxp does not raise warnings on overflow/underflow. To detect these events, check the status['overflow'] and status['underflow'] fields.

    # Set at instantiation
    x = Fxp(3.25, signed=True, n_word=16, n_frac=8, overflow='saturate')
    
    # Change behavior later
    x.overflow = 'wrap'
    
    # Check limits
    print(x.upper)
    print(x.lower)
    
    # Check for overflow/underflow status
    print(x.status['overflow'])
  10. Create Fxp variables

    develop

    You can create a fractional fixed-point variable (Fxp) by passing a value. If no bit sizes are provided, fxpmath automatically calculates the minimum bits required to represent the value.

    To define the precision explicitly, specify signed, n_word (total bits), and n_frac (fractional bits). You can also use a dtype string or Qm.n/UQm.n notation.

    Dtype Format: fxp-<sign><n_word>/<n_frac>-{complex} (e.g., fxp-s16/8, fxp-u8/1, fxp-s32/24-complex).

    from fxpmath import Fxp
    
    # Automatic sizing
    x = Fxp(-7.25)
    
    # Explicit sizing via arguments
    x = Fxp(-7.25, signed=True, n_word=16, n_frac=8)
    # or positional
    x = Fxp(-7.25, True, 16, 8)
    
    # Explicit sizing via dtype string
    x = Fxp(-7.25, dtype='fxp-s16/8')
    
    # Using Qm notation
    x = Fxp(-7.25, dtype='S8.8')
  11. Handle edge cases in Fxp sizing (negative n_frac, n_int, etc.)

    develop

    Fxp supports several non-standard sizing configurations that are useful in DSP and hardware workflows:

    1. Negative fractional bits (n_frac < 0)

    The binary point is to the right of the LSB, resulting in a coarser resolution and larger step size. Useful for scaled accumulators.

    from fxpmath import Fxp
    x = Fxp(16, signed=True, n_word=8, n_frac=-2)
    # fxp-s8/-2 9 16.0

    2. Fractional bits greater than word bits (n_frac > n_word)

    The binary point is to the left of the MSB. This results in very small representable magnitudes and a negative n_int.

    from fxpmath import Fxp
    y = Fxp(-3, signed=True, n_word=1, n_frac=10)
    # fxp-s1/10 -10 -1 -0.0009765625

    3. Negative integer bits (n_int < 0)

    This occurs when the binary point is outside the stored word (same as case 2).

    from fxpmath import Fxp
    z = Fxp(1, signed=False, n_word=4, n_frac=5)
    # fxp-u4/5 -1 15 0.46875

    4. Zero fractional bits (n_frac = 0)

    Pure integer scaling.

    from fxpmath import Fxp
    w = Fxp(7, signed=True, n_word=8, n_frac=0)
    # fxp-s8/0 7 7.0

    5. Zero integer bits (n_int = 0)

    Pure fractional-style range, common for normalized signals.

    from fxpmath import Fxp
    u = Fxp(0.75, signed=True, n_word=8, n_frac=7)
    # fxp-s8/7 0 0.75
  12. Install fxpmath

    develop

    You can install fxpmath using pip or conda. Alternatively, you can clone the repository and install it locally.

    # Using pip
    pip install fxpmath
    
    # Using conda
    conda install -c francof2a fxpmath
    
    # From source
    git clone https://github.com/francof2a/fxpmath.git
    cd fxpmath
    pip install .