Back to Number Theory Dsa

Extended Euclid & Bounded Diophantine Solutions

September 22, 2026 Wasil Zafar 10 min read

Find, parameterize, and optimize bounded linear integer solutions.

Table of Contents

  1. Intuition, Vocabulary and Foundations
  2. Step-by-Step Derivations and Algorithms
  3. Implementation, Complexity and Engineering
  4. Applications, Case Studies and Exercises

Intuition, Vocabulary and Foundations

A linear Diophantine equation asks for integer solutions to $ax+by=c$. Think of two package sizes: if items arrive only in boxes of $a$ and $b$, then $x$ and $y$ describe how many boxes of each type produce a target total $c$. Negative variables can model returns or credits; bounds such as $0\le x\le X$ turn the unrestricted equation into a practical allocation problem.

If $g=\gcd(a,b)$, every integer combination $ax+by$ is divisible by $g$. Bézout’s identity supplies integers $u,v$ satisfying $au+bv=g$. Therefore the equation has an integer solution exactly when $g\mid c$. Multiplying the identity by $c/g$ gives $x_0=u(c/g)$ and $y_0=v(c/g)$.

One solution generates every solution. With $s_x=b/g$ and $s_y=-a/g$, the complete family is $x=x_0+s_xt$ and $y=y_0+s_yt$ for $t\in\mathbb Z$. Bounds on $x$ and $y$ become integer intervals for $t$. A linear objective $px+qy$ becomes $(px_0+qy_0)+t(ps_x+qs_y)$, so its slope determines which feasible endpoint is optimal.

ConceptDefinition or invariantConcrete exampleNon-example or boundaryQuestion answered
Bézout coefficients$au+bv=\gcd(a,b)$$15(3)+21(-2)=3$Coefficients are not generally uniqueHow is one constructive gcd identity obtained?
Solvability$\gcd(a,b)\mid c$$15x+21y=84$ is solvable$15x+21y=85$ has no integer solutionDoes any integer solution exist?
Particular solutionOne pair $(x_0,y_0)$ satisfying the equationScale Bézout coefficients by $c/g$A real solution need not be integralWhere does the affine family begin?
Homogeneous step$(s_x,s_y)=(b/g,-a/g)$For $15x+21y=84$, the step is $(7,-5)$Using $(b,-a)$ skips solutions when $g\gt1$How do consecutive integer solutions differ?
Feasible interval$t_{\min}\le t\le t_{\max}$Both variable bounds intersect in one intervalIntersecting real intervals without integer roundingWhich parameters satisfy all bounds?
Objective slope$\Delta=ps_x+qs_y$If $\Delta\gt0$, choose the smallest $t$Enumerating every feasible solutionWhich endpoint minimizes a linear cost?
Signed floor$\lfloor n/d\rfloor$ for signed nonzero $d$$\lfloor-7/3\rfloor=-3$Truncation toward zero gives $-2$How is an upper bound rounded?
Signed ceiling$\lceil n/d\rceil$ for signed nonzero $d$$\lceil-7/3\rceil=-2$A positive-only shortcut failsHow is a lower bound rounded?

Step-by-Step Derivations and Algorithms

Extended Euclid maintains representations of consecutive remainders as combinations of the original inputs. Starting with $r_0=a$ and $r_1=b$, each division writes $r_{i-1}=q_ir_i+r_{i+1}$. Applying the same update to coefficient pairs preserves $r_i=ax_i+by_i$. When the remainder reaches zero, the preceding remainder is the gcd and its coefficients are Bézout coefficients.

If $g\mid c$, scale those coefficients to obtain $(x_0,y_0)$. To prove completeness, subtract the particular solution from any other solution: $a(x-x_0)+b(y-y_0)=0$. After division by $g$, coprimality of $a/g$ and $b/g$ forces $x-x_0$ to be a multiple of $b/g$, yielding the single integer parameter.

Each bound becomes $L\le z_0+st\le R$. For positive $s$, this gives $\lceil(L-z_0)/s\rceil\le t\le\lfloor(R-z_0)/s\rfloor$. For negative $s$, division reverses the inequalities, so the endpoints exchange roles. A zero step either contributes no restriction when $z_0$ is in bounds or proves infeasibility. Intersect the intervals from both variables; then choose an endpoint according to the objective slope.

Extended Euclid & Bounded Diophantine Solutions algorithm map
flowchart TD
    A["Input coefficients and bounds"] --> B["Compute g, u, v"]
    B --> C{"Does g divide c?"}
    C -->|No| D["No integer solution"]
    C -->|Yes| E["Scale one solution"]
    E --> F["Build affine step"]
    F --> G["Convert x bounds to t"]
    G --> H["Convert y bounds to t"]
    H --> I{"Intersection nonempty?"}
    I -->|No| J["No bounded solution"]
    I -->|Yes| K["Count feasible parameters"]
    K --> L{"Objective slope sign"}
    L -->|Positive| M["Choose lower endpoint"]
    L -->|Negative| N["Choose upper endpoint"]
    L -->|Zero| O["Choose either endpoint"]

Extended Euclid preserves $r=ax+by$ at every iteration, and the affine shift preserves $ax+by=c$ because $a(b/g)+b(-a/g)=0$. The algorithm uses $O(\log\min(\lvert a\rvert,\lvert b\rvert))$ arithmetic iterations, followed by $O(1)$ interval intersections and objective evaluation, with $O(1)$ auxiliary storage.

Implementation, Complexity and Engineering

The reference implementation defines floor and ceiling division for any nonzero signed denominator, constructs the affine family, intersects variable bounds, counts feasible solutions, and minimizes a linear objective. It handles one zero coefficient naturally and rejects the special case $a=b=c=0$, whose two-dimensional solution set cannot be represented by one parameter.

In fixed-width languages, intermediate coefficients may overflow even when the final solution fits. Use a wider type or checked arithmetic for Bézout updates, scaling by $c/g$, interval endpoints, and objective evaluation.

from dataclasses import dataclass


def floor_div(numerator, denominator):
    if denominator == 0:
        raise ZeroDivisionError("division by zero")
    quotient = abs(numerator) // abs(denominator)
    remainder_exists = abs(numerator) % abs(denominator) != 0
    if (numerator < 0) != (denominator < 0):
        quotient = -quotient
        if remainder_exists:
            quotient -= 1
    return quotient


def ceil_div(numerator, denominator):
    return -floor_div(-numerator, denominator)


def extended_gcd(a, b):
    old_r, r = abs(a), abs(b)
    old_x, x = 1, 0
    old_y, y = 0, 1
    while r:
        quotient = old_r // r
        old_r, r = r, old_r - quotient * r
        old_x, x = x, old_x - quotient * x
        old_y, y = y, old_y - quotient * y
    if a < 0:
        old_x = -old_x
    if b < 0:
        old_y = -old_y
    return old_r, old_x, old_y


@dataclass(frozen=True)
class SolutionFamily:
    x0: int
    y0: int
    step_x: int
    step_y: int

    def at(self, parameter):
        return (
            self.x0 + self.step_x * parameter,
            self.y0 + self.step_y * parameter,
        )


@dataclass(frozen=True)
class BoundedSolutions:
    family: SolutionFamily
    parameter_min: int
    parameter_max: int

    @property
    def count(self):
        return self.parameter_max - self.parameter_min + 1

    def endpoints(self):
        return (
            self.family.at(self.parameter_min),
            self.family.at(self.parameter_max),
        )


def solve_family(a, b, c):
    if a == 0 and b == 0:
        if c == 0:
            raise ValueError("all integer pairs solve the degenerate equation")
        return None
    g, u, v = extended_gcd(a, b)
    if c % g != 0:
        return None
    scale = c // g
    return SolutionFamily(
        x0=u * scale,
        y0=v * scale,
        step_x=b // g,
        step_y=-(a // g),
    )


def parameter_interval(base, step, lower, upper):
    if lower > upper:
        raise ValueError("lower bound exceeds upper bound")
    if step == 0:
        return None if lower <= base <= upper else ()
    if step > 0:
        first = ceil_div(lower - base, step)
        last = floor_div(upper - base, step)
    else:
        first = ceil_div(upper - base, step)
        last = floor_div(lower - base, step)
    return () if first > last else (first, last)


def solve_bounded(a, b, c, x_min, x_max, y_min, y_max):
    family = solve_family(a, b, c)
    if family is None:
        return None
    intervals = (
        parameter_interval(family.x0, family.step_x, x_min, x_max),
        parameter_interval(family.y0, family.step_y, y_min, y_max),
    )
    lower = upper = None
    for interval in intervals:
        if interval == ():
            return None
        if interval is None:
            continue
        current_lower, current_upper = interval
        lower = current_lower if lower is None else max(lower, current_lower)
        upper = current_upper if upper is None else min(upper, current_upper)
    if lower is None or upper is None:
        raise ValueError("unbounded parameter interval")
    if lower > upper:
        return None
    return BoundedSolutions(family, lower, upper)


def minimize_linear(bounded, coefficient_x, coefficient_y):
    slope = (
        coefficient_x * bounded.family.step_x
        + coefficient_y * bounded.family.step_y
    )
    parameter = (
        bounded.parameter_min if slope >= 0 else bounded.parameter_max
    )
    x, y = bounded.family.at(parameter)
    return x, y, coefficient_x * x + coefficient_y * y


def brute_solutions(a, b, c, x_min, x_max, y_min, y_max):
    return [
        (x, y)
        for x in range(x_min, x_max + 1)
        for y in range(y_min, y_max + 1)
        if a * x + b * y == c
    ]


g, u, v = extended_gcd(15, 21)
assert g == 3 and 15 * u + 21 * v == g

bounded = solve_bounded(15, 21, 84, 0, 20, -10, 10)
expected = brute_solutions(15, 21, 84, 0, 20, -10, 10)
generated = [
    bounded.family.at(t)
    for t in range(bounded.parameter_min, bounded.parameter_max + 1)
]
assert generated == expected
assert bounded.count == len(expected)

best = minimize_linear(bounded, 2, 1)
assert best == min(
    ((x, y, 2 * x + y) for x, y in expected),
    key=lambda item: item[2],
)

assert solve_bounded(15, 21, 85, -100, 100, -100, 100) is None
assert solve_bounded(0, 7, 21, -2, 2, 3, 3).count == 5
assert solve_bounded(7, 0, 21, 3, 3, -2, 2).count == 5

for numerator in range(-12, 13):
    for denominator in range(-5, 6):
        if denominator == 0:
            continue
        floor_value = floor_div(numerator, denominator)
        ceil_value = ceil_div(numerator, denominator)
        assert floor_value <= numerator / denominator <= ceil_value
        assert ceil_value in (floor_value, floor_value + 1)

try:
    solve_family(0, 0, 0)
    raise AssertionError("two-dimensional degenerate family was accepted")
except ValueError:
    pass

print("all tests passed")
Implementation choiceComplexityNumeric boundaryFailure modeMitigation
Iterative extended Euclid$O(\log\min(\lvert a\rvert,\lvert b\rvert))$ divisionsCoefficients may approach input magnitudeAdjusting gcd sign but not coefficientsVerify $ax+by=g$
Affine parameterization$O(1)$ after gcdScaling by $c/g$ may overflowUsing $(b,-a)$ instead of reduced stepsDivide both steps by $g$
Signed floor division$O(1)$Minimum signed value divided by $-1$ may overflowTruncation toward zeroUse a proven helper and checked arithmetic
Signed ceiling division$O(1)$Negation may overflow in fixed-width codePositive-only shortcutUse quotient and remainder or a wider type
Bound intersection$O(1)$Inclusive endpoints require exact roundingIgnoring negative step directionBranch on the step sign and test endpoints
Objective optimization$O(1)$Objective products may overflowChoosing from coefficient signs aloneCompute the parameter slope in a wider type
Brute-force oracleProduct of bounded widthsPractical only for small rangesUsing it as the production solverReserve it for differential tests
Degenerate coefficientsProblem-specificSolution set can be two-dimensionalDividing by $\gcd(0,0)$Handle zero coefficients explicitly

Applications, Case Studies and Exercises

Bounded Diophantine equations model package selection, resource allocation, synchronization offsets, currency combinations, and integer production plans. Their unrestricted algebra is only the first half of the problem: bounds determine feasibility, and the objective slope determines which feasible endpoint is operationally best.

Practice

Apply Extended Euclid & Bounded Diophantine Solutions

Case study 1 — package planning. Solve $15x+21y=84$ with $0\le x\le20$ and $-10\le y\le10$. Compute one Bézout solution, derive the step $(7,-5)$, and intersect both parameter intervals. Checkpoint: compare the complete family with a bounded brute-force search. Success means every generated pair satisfies the equation and every oracle solution appears once.

Case study 2 — minimum-cost production. A service combines batches of sizes $144$ and $252$ to meet a net requirement $c$, with signed adjustments allowed inside contractual bounds. Given costs $p$ and $q$, solve $144x+252y=c$ and choose the feasible endpoint from the slope $p(252/g)-q(144/g)$. Reject targets not divisible by $g$, verify both endpoints, and compare small replicas with exhaustive search.

Hands-on signed-rounding exercise. Test every numerator from $-20$ through $20$ and every nonzero denominator from $-7$ through $7$. Verify the defining floor and ceiling inequalities, then solve randomly generated bounded equations and compare feasibility, count, and minimum objective with brute force. Success means exact agreement for at least one thousand seeded cases, including negative coefficients and bounds.

Tiered exercises. Recall: state the divisibility criterion. Derive: prove every solution differs by $(b/g,-a/g)t$. Implement: return the lexicographically smallest feasible pair. Debug: find a counterexample to a positive-only ceiling formula. Extend: minimize $\lvert px+qy-r\rvert$ by checking feasible parameters nearest its real breakpoint and both interval endpoints.

Use extended Euclid for one constructive solution, reduce all remaining freedom to the integer parameter $t$, intersect signed bounds with proven floor and ceiling helpers, and optimize from the resulting slope. Common failures are skipping the gcd test, omitting division by $g$ in the step, applying positive-only rounding formulas, and failing to verify the selected endpoint in the original equation.