Skip to content

Definition of widemul in the package #353

Open

Description

@PatrickHaecker

This is how multiplication of fractional values works in binary numbers:

  • multiplying two numbers with i1 and i2 (possibly leading zeros, possibly sign-including) integer bits, the result will have at most i1 + i2 integer bits.
  • multiplying two numbers with f1 and f2 (possibly trailing zeros) fractional digits, the result will have at most f1 + f2 fractional bits.

So the only general, type-safe definition (independent of the values) which is aligned to "doubling the number of bits from Base" is the definition of the result type to have i1 + i2 integer bits and f1 + f2 fractional bits.

Activity

  1. kimikage commented on Oct 10, 2026

    @kimikage
    Collaborator

    With a top-down approach, OP seems correct. On the other hand, with a bottom-up approach, a different definition also seems reasonable.

    Fixed-point numbers possess characteristics intermediate between integers and floating-point numbers. If both behave in the same way, it is implicitly expected that fixed-point numbers will also behave similarly.

    For the sake of simplicity, let us first consider the case where the types of the two operands are identical.
    For *, the output type is the same as the input type.

    julia> typeof(Int8(-128) * Int8(-128))
    Int8
    
    julia> typeof(-128f0 * -128f0)
    Float32
    
    julia> typeof(Q7f8(-128) * Q7f8(-128))
    Q7f8

    Of course, the situation regarding * cannot be said to be entirely clear. There have been similar discussions in the past.(cf. #227 (comment))

    Next, let's look at widemul, which could be considered a variant of *.

    julia> typeof(widemul(Int8(-128), Int8(-128))) === widen(Int8)
    true
    
    julia> typeof(widemul(-128f0, -128f0)) === widen(Float32)
    true

    widen(Q7f8) is expected to result in Q23f8 (because it is based on addition/subtraction rather than multiplication), and it differs from Q15f16.

  2. kimikage commented on Oct 10, 2026

    @kimikage
    Collaborator

    The reasoning in the comment above breaks down when the operands are Fixed or Normed types with different fractional bits f.
    However, this does not mean that the bottom-up approach is incorrect. Rather, it is a practical issue with the top-down definition.

    Attempting to satisfy both dynamic range and precision easily leads to the use of 128-bit types, marking the end of the road.
    For this reason, the current promotion_rule is based on max(f1, f2) rather than f1 + f2.

    Three variants can be considered here:

    1. Define widemul only when the two operands have completely identical types.
    2. Define widemul only when f1 == f2.
    3. Define widemul for all cases with promotion.
  3. PatrickHaecker commented on Oct 11, 2026

    @PatrickHaecker
    CollaboratorAuthor

    Sorry, I couldn't follow your argumentation here.

    widen is the additive widening that needs one additional bit for identical BitIntegers, but, per convention and for pragmatic reasons, doubles the size of Base.BitInteger64_types.

    widemul is the multiplicative widening that (besides actually doing the multiplication) really needs twice the number of bits for identical BitIntegers.

    So widemul is no variant of widen (in the sense that you cannot use either to reason about the other) as much as one (multiplicative identity) is not a variant of oneunit (one-unit step value).

    promote and promote_rule are a third thing which only demand a common type. It neither needs to be wide enough for addition nor for multiplication.

    As already written: You can define different interfaces/contracts, and there are quite some useful definitions, but then it's no longer widemul, but a different function (e.g. Base.mul_hi).

  4. kimikage commented on Oct 11, 2026

    @kimikage
    Collaborator

    Ultimately, since your explanation is based on a top-down definition, it serves only to reinforce the "correctness" of the OP.
    I am (or the second post is) not denying that the definition in the OP.
    The point here should be practicality rather than correctness.

    From a practical standpoint, the fact remains that, regardless of which definition is adopted, the implementation of widemul for Normed is inherently cumbersome.
    If manually performing type conversions or using Float16 or Float32 is considered best practice, then the point of implementing widemul is undermined.
    On the other hand, while Fixed also involves rounding issues, shifting the decimal point position is not particularly complex.
    Paradoxically, I don't think people are clamoring for widemul for Fixed.

    IMO, not implementing widemul is a reasonable option.

  5. kimikage commented on Oct 11, 2026

    @kimikage
    Collaborator

    @timholy might have some strong opinions on "widen arithmetic."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions