Why pure Go¶
go-ruby-rational/rational reimplements Ruby's Rational in pure Go, with cgo
disabled. The number type it covers is deterministic and interpreter-independent:
given a numerator and denominator (or a string), every result is a pure function of those
inputs — no live binding, no evaluation of arbitrary Ruby. That is exactly the part that
can — and should — live as a standalone Go library, separate from the interpreter.
Why a separate, MRI-faithful type¶
The exact rational core (reduce to lowest terms, exact + - * /, integer **) is the
easy part; matching MRI is in the details, and those details are why this is a
distinct, MRI-pinned type rather than a generic rational:
inspectprints(3/4)whileto_sprints3/4(String()returns the inspect form);- rounding is half-away-from-zero (
(5/2).round == 3,(-5/2).round == -3); - the digit-aware
floor/ceil/round/truncatereturn a Rational forn >= 1but an Integer forn <= 0; **stays exact for an Integer exponent yet falls back to Float for a Rational/Float exponent.
This is why it is deliberately distinct from go-composites/rational, which models a generic mathematical rational rather than Ruby's exact semantics. This package encodes MRI's rules and pins them with a differential oracle.
Extracted from rbgo, reusable by anyone¶
It is the Rational backend bound into
go-embedded-ruby by rbgo, but is a
standalone, reusable library so that:
- any Go program can import
github.com/go-ruby-rational/rationaldirectly, with no Ruby runtime; - the dependency runs the other way —
rbgobinds this module as a native module (the same pattern as go-ruby-bigdecimal), rather than this module depending on the interpreter; - the behaviour is pinned by a differential oracle against the system
ruby, independent of any one consumer.
Why pure Go matters here¶
Because the library is CGO-free and dependency-free (a math/big.Rat core only), it:
- cross-compiles to every Go target with no C toolchain, and links into a single static binary;
- has no dependency on the Ruby runtime — the dependency runs the other way;
- can be differentially tested against the
rubybinary wherever one is onPATH, while the cross-arch and Windows lanes (whererubyis absent) still validate the library.
A note on the libm boundary
Pow with an integer exponent is computed exactly. For a Rational or Float
exponent MRI returns a Float (a.to_f ** exp); PowFloat provides that fallback
via math.Pow. Because Go's math.Pow and MRI's C pow are independent libm
implementations, a perfect-root case can differ in the last ULP across platforms — the
oracle therefore exercises Pow (exact) and only the libm-stable PowFloat cases.
See Usage & API for the surface and Roadmap for what is in scope.