Design Philosophy
simple_fraction follows the simple_* library philosophy: provide a clean, focused API for a single purpose. Unlike simple_decimal (which wraps Gobo's MA_DECIMAL), simple_fraction is a pure Eiffel implementation with no external dependencies.
Core Principles
- Exactness - No approximation; 1/3 is exactly 1/3
- Auto-reduction - Fractions always in lowest terms
- Immutability - All operations return new values
- Safety - Design by Contract throughout
- No dependencies - Pure Eiffel, just needs base library
Internal Representation
Data Structure
class
SIMPLE_FRACTION
feature -- Access
numerator: INTEGER_64
-- The numerator (top number), may be negative
denominator: INTEGER_64
-- The denominator (bottom number), always positive
invariant
denominator_positive: denominator > 0
reduced_form: is_reduced
end
Why INTEGER_64?
INTEGER_64 provides a range of roughly ±9.2 quintillion, which handles most practical calculations. For numerator/denominator pairs:
- Can represent fractions with large numerators and denominators
- Avoids overflow for most arithmetic operations
- Simple, efficient integer math (no BigInteger needed)
Normalization Rules
- Denominator always positive - Sign stored in numerator only
- Always reduced - GCD of numerator and denominator is 1
- Zero normalized - 0/n always becomes 0/1
-- Examples of normalization:
make (2, 4) -- Stored as 1/2 (reduced by GCD 2)
make (-3, 4) -- Stored as -3/4 (sign in numerator)
make (3, -4) -- Stored as -3/4 (sign moved to numerator)
make (-3, -4) -- Stored as 3/4 (negatives cancel)
make (0, 5) -- Stored as 0/1 (zero normalized)
GCD Algorithm
Fraction reduction uses the Euclidean algorithm for finding the Greatest Common Divisor:
gcd (a, b: INTEGER_64): INTEGER_64
-- Greatest common divisor using Euclidean algorithm
require
a_non_negative: a >= 0
b_non_negative: b >= 0
local
l_a, l_b, l_temp: INTEGER_64
do
l_a := a
l_b := b
from
until
l_b = 0
loop
l_temp := l_b
l_b := l_a \\ l_b
l_a := l_temp
end
Result := l_a
if Result = 0 then
Result := 1
end
ensure
result_positive: Result > 0
end
Algorithm Trace
Example: gcd(48, 18)
- 48 mod 18 = 12, so now find gcd(18, 12)
- 18 mod 12 = 6, so now find gcd(12, 6)
- 12 mod 6 = 0, so GCD is 6
Therefore 48/18 reduces to 8/3 (divided by GCD 6).
Arithmetic Implementation
Addition and Subtraction
-- a/b + c/d = (a*d + c*b) / (b*d)
add alias "+" (other: like Current): SIMPLE_FRACTION
local
l_num: INTEGER_64
do
l_num := numerator * other.denominator + other.numerator * denominator
create Result.make (l_num, denominator * other.denominator)
end
Multiplication
-- a/b * c/d = (a*c) / (b*d)
multiply alias "*" (other: like Current): SIMPLE_FRACTION
do
create Result.make (numerator * other.numerator, denominator * other.denominator)
end
Division
-- a/b / c/d = a/b * d/c = (a*d) / (b*c)
divide alias "/" (other: like Current): SIMPLE_FRACTION
require
other_not_zero: not other.is_zero
do
create Result.make (numerator * other.denominator, denominator * other.numerator)
end
Note: The make constructor automatically reduces, so results
are always in lowest terms.
Comparison Implementation
Comparison uses cross-multiplication to avoid division:
-- a/b < c/d iff a*d < c*b (when denominators positive)
is_less alias "<" (other: like Current): BOOLEAN
do
Result := numerator * other.denominator < other.numerator * denominator
end
Since denominators are always positive (invariant), this comparison is safe and doesn't require sign adjustment.
Mixed Number Handling
Mixed numbers are converted to improper fractions on creation:
make_mixed (a_whole, a_numerator, a_denominator: INTEGER_64)
-- Create from mixed number: whole + numerator/denominator
local
l_num: INTEGER_64
do
if a_whole >= 0 then
l_num := a_whole * a_denominator + a_numerator
else
l_num := a_whole * a_denominator - a_numerator
end
set_and_reduce (l_num, a_denominator)
end
Converting Back to Mixed
to_mixed_string: STRING
-- Format as "whole num/den" or "num/den"
local
l_whole, l_frac_num: INTEGER_64
do
if is_proper or is_zero or is_integer then
Result := out
else
l_whole := whole_part -- 11/4 -> 2
l_frac_num := (numerator - l_whole * denominator).abs -- -> 3
Result := l_whole.out + " " + l_frac_num.out + "/" + denominator.out
end
end
String Parsing
The parser handles multiple formats:
| Format | Example | Detection |
|---|---|---|
| Simple fraction | "3/4" | Contains '/' but no space |
| Mixed number | "2 3/4" | Contains both ' ' and '/' |
| Decimal | "0.5" | Contains '.' |
| Integer | "5" | None of the above |
Decimal Conversion
Decimals become fractions with power-of-10 denominators, then reduce:
-- "0.5" -> 5/10 -> 1/2
-- "0.25" -> 25/100 -> 1/4
-- "0.125" -> 125/1000 -> 1/8
Immutability Pattern
All arithmetic operations return new SIMPLE_FRACTION objects:
Benefits
- Thread Safety - No shared mutable state
- Predictability - Values never change unexpectedly
- Debugging - Original values preserved
- Chaining - Natural composition of operations
-- Each operation creates new value
a := a + b -- 'a' now references new sum
-- Chaining is natural
result := (a + b) * c / d
Design by Contract
Class Invariants
invariant
denominator_positive: denominator > 0
reduced_form: is_reduced
Key Preconditions
-- Division requires non-zero divisor
divide alias "/" (other: like Current): SIMPLE_FRACTION
require
other_not_zero: not other.is_zero
-- Reciprocal requires non-zero
reciprocal: SIMPLE_FRACTION
require
not_zero: not is_zero
-- Creation requires valid denominator
make (a_numerator, a_denominator: INTEGER_64)
require
denominator_not_zero: a_denominator /= 0
Key Postconditions
-- Reduction always happens
make (a_numerator, a_denominator: INTEGER_64)
ensure
is_reduced: is_reduced
denominator_positive: denominator > 0
-- Absolute value is non-negative
absolute: SIMPLE_FRACTION
ensure
result_non_negative: not Result.is_negative
-- Reciprocal property
reciprocal: SIMPLE_FRACTION
ensure
reciprocal_property: (Current * Result).is_equal (one)
SCOOP Compatibility
simple_fraction is designed for SCOOP:
- Immutable operations - No shared mutable state
- Void safety - All attributes are attached
- No global state - Each instance self-contained
<capability>
<concurrency support="scoop" use="scoop"/>
<void_safety support="all" use="all"/>
</capability>
Dependencies
Minimal
- base - EiffelStudio standard library (only dependency)
Unlike simple_decimal (which wraps Gobo MA_DECIMAL), simple_fraction is a pure Eiffel implementation with no external dependencies.
File Structure
simple_fraction/
├── simple_fraction.ecf -- Project configuration
├── src/
│ └── simple_fraction.e -- Main class (~500 lines)
├── tests/
│ └── simple_fraction_tests.e -- Test suite (66 tests)
├── docs/
│ ├── index.html
│ ├── user-guide.html
│ ├── api-reference.html
│ ├── architecture.html
│ ├── cookbook.html
│ ├── css/style.css
│ └── images/logo.svg
├── README.md
└── LICENSE