Skip to main content

Exact complex-like scalars with rational real and imaginary parts.

Project description

gaussian-rational

CI PyPI version Python versions License: MIT

gaussian-rational provides an exact, immutable complex-like scalar where both components are rational numbers (fractions.Fraction).

Use GaussianRational when you want complex arithmetic without intermediate floating-point rounding in the real and imaginary parts.

Highlights

  • Exact real and imaginary components (Fraction-backed).
  • Immutable value type, safe to share across threads.
  • Complex-style arithmetic (+, -, *, /, integer **).
  • Compatible real equality/hash behavior for purely real values.
  • Flexible string parsing and formatting.
  • Fully typed (PEP 561, inline annotations).

Installation

pip install gaussian-rational

Quick Start

from fractions import Fraction

from gaussian_rational import GaussianRational

z = GaussianRational(Fraction(1, 3), Fraction(2, 5))
w = GaussianRational(2, -1)

print(z + w)          # 7/3-3j/5
print(z * w)          # 16/15+7j/15
print(z / w)          # 4/15+j/3
print(z.conjugate())  # 1/3-2j/5
print(z.real, z.imag) # Fraction(1, 3) Fraction(2, 5)
print(complex(z))     # (0.3333333333333333+0.4j)
print(z.arg())        # atan2(float(imag), float(real))

Construction

GaussianRational normalizes several input forms:

from fractions import Fraction
from gaussian_rational import GaussianRational

GaussianRational(3, 4)                           # a=3, b=4
GaussianRational((Fraction(1, 2), Fraction(2)))  # tuple input
GaussianRational(5)                              # purely real, imag=0
GaussianRational(GaussianRational(1, 2))         # identity/upcast
GaussianRational("1/2+2j")                       # string parse

Accepted component types are int and Fraction.

API Summary

Numeric operations

  • +, -, unary -
  • *, /
  • Integer exponentiation **n for n: int
  • abs(x) returns float
  • complex(x) returns built-in complex using float casts of both parts

Properties and helpers

  • real, imag — rational components, mirroring complex
  • conjugate() — complex conjugate a - bi
  • arg() — phase angle in radians (atan2(imag, real))
  • as_tuple() — explicit (real, imag) tuple
  • abs_squared() — exact norm-squared as Fraction
  • Predicates: is_real, is_imaginary, is_zero_or_imaginary, is_composite, is_zero
  • Parsing: GaussianRational.parse(...)
  • Formatting: format(...), str(...), repr(...)

If you want deterministic ordering, sort explicitly with as_tuple():

sorted_values = sorted(values, key=lambda z: z.as_tuple())

String Formatting

GaussianRational provides three string surfaces:

Surface Description
str(z) Calls z.format().
z.format(...) Configurable exact symbolic output.
format(z, spec) / f-strings Empty spec → symbolic; non-empty spec → float-based complex semantics.
repr(z) Eval-safe constructor form, for example GaussianRational(Fraction(1, 2), 3).

Formatting rules:

  • Real-only values print as rational scalars (for example 3, -1/2).
  • Imaginary-only values print with j attached (for example j, -2j, j/3, -5j/7).
  • Composite values print as a+bj or a-bj with no spaces.
  • force_sign=True adds a leading + for non-negative outputs.
  • parens_if_composite=True wraps composite outputs in parentheses; for non-composite values, rational fractions are parenthesized instead.
  • imag_char overrides the symbol per call (for example imag_char="i").
  • GaussianRational.default_imag_char sets the class-wide default.
  • For format(z, spec): non-empty spec uses complex formatting semantics, mapping the imaginary symbol to the class default.

Examples:

from fractions import Fraction
from gaussian_rational import GaussianRational

str(GaussianRational(3, 0))                               # "3"
str(GaussianRational(0, 1))                               # "j"
str(GaussianRational(1, -2))                              # "1-2j"
GaussianRational(1, 2).format(force_sign=True)            # "+1+2j"
GaussianRational(Fraction(1, 2), Fraction(1, 3)).format(
    parens_if_composite=True,
)                                                         # "(1/2+j/3)"
GaussianRational(1, 2).format(imag_char="i")              # "1+2i"
repr(GaussianRational(Fraction(1, 2), Fraction(-3, 4)))   # "GaussianRational(Fraction(1, 2), Fraction(-3, 4))"
f"{GaussianRational(Fraction(1, 2), Fraction(-5, 3)):.2f}" # "0.50-1.67j"

repr(z) is an eval-safe round-trip given from fractions import Fraction and from gaussian_rational import GaussianRational in scope.

Parsing String Literals

GaussianRational.parse parses symbolic literals and is also used by GaussianRational("...").

from fractions import Fraction
from gaussian_rational import GaussianRational

GaussianRational.parse("1+2j")                 # GaussianRational(1, 2)
GaussianRational.parse("(2/3)j")               # GaussianRational(0, Fraction(2, 3))
GaussianRational.parse("2j/3")                 # GaussianRational(0, Fraction(2, 3))
GaussianRational.parse(" 1/2 + 3 i ", imag_char=("i", "j"))
GaussianRational.parse("2/3j", interpret_slash_j_as_j_slash=True)

Parsing notes:

  • Embedded spaces are ignored.
  • imag_char accepts a single character or a tuple of aliases.
  • By default, ambiguous 2/3j is rejected; set interpret_slash_j_as_j_slash=True to accept it as 2j/3.
  • GaussianRationalLike intentionally excludes str, so arithmetic dunder upcasting never treats arbitrary strings as numeric.

Examples

Conjugate product gives exact norm-squared

from fractions import Fraction
from gaussian_rational import GaussianRational

z = GaussianRational(Fraction(3, 4), Fraction(-5, 6))
prod = z * z.conjugate()

print(prod)             # 181/144
print(prod.is_real)     # True
print(prod.real)        # Fraction(181, 144)
print(z.abs_squared())  # Fraction(181, 144)

Division remains exact

from fractions import Fraction
from gaussian_rational import GaussianRational

x = GaussianRational(Fraction(1, 2), Fraction(1, 3))
y = GaussianRational(Fraction(2, 5), Fraction(-1, 7))

q = x / y
print(q.real)  # Fraction(1295, 1222)
print(q.imag)  # Fraction(980, 1833)

Integer powers

from gaussian_rational import GaussianRational

z = GaussianRational(1, 1)
print(z ** 2)   # 2j
print(z ** -1)  # 1/2-j/2

Interop with built-in complex

from fractions import Fraction
from gaussian_rational import GaussianRational

z = GaussianRational(Fraction(1, 3), Fraction(5, 2))
c = complex(z)

print(c)        # (0.3333333333333333+2.5j)
print(type(c))  # <class 'complex'>

Semantics and Compatibility

GaussianRational is designed to feel close to complex while preserving exact rational components.

  • Truthiness matches numeric convention: only GaussianRational(0, 0) is false.
  • Equality accepts GaussianRational, int, and Fraction values.
  • Equality intentionally does not accept tuples: GaussianRational(1, 2) == (1, 2) is False.
  • Hashing is aligned for purely real values so equality and hash stay consistent with compatible real scalars.
  • Instances are immutable and safe to share across threads.

Current intentional limitation:

  • Exponentiation supports integer powers only.

Type Hints

This package ships inline type hints and a py.typed marker (PEP 561).

Public typing aliases:

  • FractionLike = Fraction | int
  • GaussianRationalLike = GaussianRational | tuple[FractionLike, FractionLike] | FractionLike

Advanced Usage

Subclassing

GaussianRational is immutable and uses __slots__ for compact instances. Subclassing is supported with a few rules:

  • Do not assign attributes normally (self.x = ...) after construction; use object.__setattr__ in __new__ instead.
  • If your subclass adds fields, declare its own __slots__.
  • Keep type(self)(a, b) compatible with your subclass constructor, since arithmetic results are constructed that way.

Minimal pattern:

from gaussian_rational import GaussianRational


class TaggedGaussianRational(GaussianRational):
    __slots__ = ("tag",)

    def __new__(cls, v, v2=None, *, tag: str = ""):
        self = super().__new__(cls, v, v2)
        object.__setattr__(self, "tag", tag)
        return self

Subclass contract for repr round-trips:

__repr__ is implemented as f"{type(self).__name__}({self.format()!r})". A subclass that adds state beyond the real and imag slots must override both format() (to encode that state in the string) and parse() (to decode it), so that eval(repr(z)) remains a valid round-trip.

Common pitfalls:

  • Forgetting __slots__ on the subclass, which reintroduces __dict__.
  • Changing constructor semantics so type(self)(a, b) no longer works.
  • Adding mutable fields while keeping hashing enabled (invalidates hash contract if mutable fields affect equality).

Supported Python Versions

Python 3.10 and later.

License

MIT. See LICENSE.


For development and release workflow documentation, see CONTRIBUTING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gaussian_rational-1.0.1.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gaussian_rational-1.0.1-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file gaussian_rational-1.0.1.tar.gz.

File metadata

  • Download URL: gaussian_rational-1.0.1.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gaussian_rational-1.0.1.tar.gz
Algorithm Hash digest
SHA256 1b29eb972f9f19bbe5ed5c2bd89f7245185ad2636f472e24730854b58c664f72
MD5 2bcbe2f254d9a36110362c6a2c421c72
BLAKE2b-256 940c208fc3be543e93dae476704a8065c29eec60b2468bf41f00761d25024c04

See more details on using hashes here.

Provenance

The following attestation bundles were made for gaussian_rational-1.0.1.tar.gz:

Publisher: publish.yml on mckelvie-org/gaussian-rational

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gaussian_rational-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for gaussian_rational-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a5b172d83de8d34c14dac81f498638a4cf08e6a24f818e7d8de86ad61b413894
MD5 68869f25c7fd522276e259a4b37b5879
BLAKE2b-256 c3934f41220be738b1504256a88bf2dee5f2fdbdd98a6c1202e93744d2d35542

See more details on using hashes here.

Provenance

The following attestation bundles were made for gaussian_rational-1.0.1-py3-none-any.whl:

Publisher: publish.yml on mckelvie-org/gaussian-rational

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page