Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

194 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fraction

Lossless fractions and decimals for Rust; an aspirational drop-in replacement for floating-point types.

GitHub Actions Documentation Current Version on crates.io MSRV Licence

Overview

fraction provides:

  • Fraction, a rational type designed for float-like arithmetic with exact values
  • Decimal, a lossless decimal representation with explicit precision
  • DynaInt, a dynamically growing integer for checked maths
  • hashable and orderable fractions, including deterministic ordering for NaN
  • PostgreSQL, Juniper, Serde, Unicode, and approximate maths support via features

Install

[dependencies]
fraction = "0.16.0"

Enable optional integrations explicitly:

[dependencies]
fraction = { version = "0.16.0", features = ["with-postgres-support", "with-serde-support"] }

Features

The default feature set enables the features marked Yes below.

Feature Default? Overview
with-bigint Yes Adds num::BigInt/BigUint support, re-exports those types, and enables aliases such as BigFraction.
with-decimal Yes Adds GenericDecimal and decimal aliases such as Decimal, with precision retained for formatting and comparison.
with-dynaint Yes Adds DynaInt, which keeps small integers inline and promotes them to a larger backing type on overflow.
with-approx No Enables with-bigint and adds accuracy-controlled square-root helpers for fractions and, when enabled, decimals.
with-juniper-support No Adds Juniper GraphQL scalar, input, and output implementations for the enabled fraction and decimal types.
with-postgres-support No Adds PostgreSQL NUMERIC ToSql/FromSql conversions for the enabled fraction and decimal types.
with-serde-support No Adds Serde Serialize/Deserialize implementations for enabled fraction, decimal, sign, and dynamic-integer types.
with-unicode No Adds Unicode fraction formatting and parsing helpers for GenericFraction.

MSRV note: Rust 1.70 is the declared minimum and is CI-tested for the with-bigint/with-approx surface exercised by the MSRV smoke harness. Latest stable is tested with all features enabled. Optional integration dependency graphs, including with-juniper-support and with-postgres-support, may require a newer compiler under fresh dependency resolution.

Unlike primitive floats, Fraction treats NaN as equal to itself and orders it below negative infinity. That makes fractions usable in sets, hash maps, and B-trees.

Examples

Fraction

use std::str::FromStr;
use fraction::{Fraction, One, Zero, Sign};

let f = Fraction::new(1u8, 2u8);
assert_eq!(f, Fraction::new_generic(Sign::Plus, 1i32, 2u8).unwrap());
assert_eq!(f, Fraction::from(0.5));
assert_eq!(f, Fraction::from_str("0.5").unwrap());
assert_eq!(f, Fraction::from_str("1/2").unwrap());
assert_eq!(Fraction::from_str("1/0").unwrap(), Fraction::infinity());
assert_eq!(Fraction::from_str("-1/0").unwrap(), Fraction::neg_infinity());
assert_eq!(Fraction::from_str("0/0").unwrap(), Fraction::nan());
assert_eq!(f * 2, Fraction::one());
assert_eq!(f - f, Fraction::zero());

Decimal

use std::str::FromStr;
use fraction::{Decimal, Fraction};

let d = Decimal::from(1);
assert_eq!(d, Decimal::from_fraction(Fraction::from(1)));

let d = Decimal::from(1.3);
assert_eq!(d, Decimal::from_str("1.3").unwrap());

let d = Decimal::from(0.5);
// Decimal fraction notation uses the same zero-denominator mapping as `Fraction::from_str`.
assert_eq!(d, Decimal::from_str("1/2").unwrap());
assert_eq!(Decimal::from_str("1/0").unwrap(), Decimal::infinity());
assert_eq!(Decimal::from_str("-1/0").unwrap(), Decimal::neg_infinity());
assert_eq!(Decimal::from_str("0/0").unwrap(), Decimal::nan());

let one_third = Fraction::new(1u8, 3u8);
assert_eq!(
    Decimal::from_fraction_with_precision(one_third, 4).to_string(),
    "0.3333"
);

let lhs = Decimal::from(0.5) / Decimal::from(0.3); // 5/3, precision 1
let rhs = Decimal::from_str("1.6").unwrap(); // 8/5, precision 1
assert_eq!(lhs, rhs);
assert_eq!(lhs.partial_cmp(&rhs), Some(std::cmp::Ordering::Equal));

Decimals compare by truncating fractional digits to each value’s own precision. Trailing zeroes are ignored, p0 drops all fractional digits, and canonical zero values such as -0, -0.9@p0, and -0.04@p1 compare and hash as positive zero.

Formatting

type F = fraction::Fraction;

let result = F::from(0.7) / F::from(0.4);
assert_eq!(format!("{}", result), "7/4");
assert_eq!(format!("{:.2}", result), "1.75");
assert_eq!(format!("{:#.3}", result), "1.750");

Unicode

When with-unicode is enabled, Unicode display helpers are available.

#[cfg(feature = "with-unicode")]
{
    type F = fraction::Fraction;

    let res = F::from(0.7) / F::from(0.4);
    assert_eq!("7⁄4", format!("{}", res.get_unicode_display()));
    assert_eq!("⁷/₄", format!("{}", res.get_unicode_display().supsub()));
}

PostgreSQL notes

Use Decimal rather than Fraction for PostgreSQL work where possible. PostgreSQL’s binary protocol uses i16, so the base type for GenericFraction or GenericDecimal should be at least u16.

For very large or repeating values such as 1/3 or 1/7, Fraction can grow to 16383 digits after the decimal point, which is slower than an explicitly precision-bound Decimal. If you need dynamic growth, DynaInt<u8, _> or DynaInt<usize, BigUint> can help.

Infinity values round-trip using PostgreSQL numeric infinity markers from PostgreSQL 14+ (0xD000 and 0xF000). Older PostgreSQL versions do not support these values, and constrained NUMERIC columns (with declared precision/scale limits) reject infinity as out of range.

Documentation

Licence

Licensed under either of:

About

[Rust] Lossless fractions and decimals; drop-in float replacement

Topics

Resources

Stars

94 stars

Watchers

4 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages