fraction/lib.rs
1#![doc(test(attr(deny(warnings), allow(deprecated))))]
2
3//! Fraction is designed to be a precise lossless drop-in replacement for floating types (f32, f64).
4//!
5//! It comes with a number of predefined type aliases covering the most common use cases such as
6//! [Fraction], [Decimal], [BigFraction], [DynaDecimal] and so on (see [prelude] module for more examples).
7//!
8//! The public API provides you with the generic types that you may use straightforwardly to build your
9//! own types, suiting your needs best (see [prelude] module for the examples).
10//!
11//! # Library features
12//!
13//! - Drop in replacement for floats with the exception for NaN == NaN so that it's hashable
14//! - It's hashable, so may be used as values in Sets and keys in dictionaries and hash maps
15//! - [Display](fraction::display) implementation for fractions and decimals
16//! - [Fraction](GenericFraction) type, representing fractions
17//! - [Decimal](GenericDecimal) type, based on [Fraction](GenericFraction) type represents floats as lossless decimals
18//! - [DynaInt](dynaint) implements dynamically growing integer type that perfarms checked math and avoids stack overflows
19//! - PostgreSQL binary protocol integration for both fractions and decimals
20//! - Juniper support for both fractions and decimals
21//! - [Generic integer conversions](generic), such as `i8 -> u8`, `usize -> u8` and so on
22//! - [Lossless division](division) with no allocations and infinite precision
23//!
24//! # Disclaimer
25//! Even though we do our best to keep it well covered with tests, there may be bugs out there.
26//! The library API is still in flux. When it gets stable we will release the version 1.0.0.
27//! You may find more info about Semantic Versioning on [https://semver.org/](https://semver.org/).
28//! Bug reports and contributions are appreciated.
29//!
30//! # Crate features
31//! - `with-bigint` (default) integration with [num::BigInt] and [num::BigUint] data types
32//! - `with-decimal` (default) [Decimal] type implemented upon [GenericFraction]
33//! - `with-dynaint` (default) dynamically growing integer avoiding stack overflows
34//! - `with-unicode` Unicode formatting and parsing options
35//! - `with-approx` adds methods for approximate computations (currently `sqrt`)
36//! - `with-juniper-support` [Juniper](https://crates.io/crates/juniper) integration
37//! - `with-postgres-support` [PostgreSQL](https://crates.io/crates/postgres) integration; Numeric/Decimal type
38//! - `with-serde-support` [Serde](https://crates.io/crates/serde) traits implementation
39//!
40//! # Implementation
41//! Basic math implemented upon the [num] crate (in particular the [num::rational] module).
42//! The utilised traits from the [num] crate are re-exported, so you don't have to explicitly depend on that crate however,
43//! you may import them from either of crates if necessary.
44//!
45//! # Usage
46//! To start using types see the [Prelude](self::prelude) module.
47//!
48//! # Examples
49//!
50//! ## Simple use:
51//!
52//! ```
53//! type F = fraction::Fraction; // choose the type accordingly to your needs (see prelude module docs)
54//!
55//! let two = F::from(0) + F::from(2); // 0 + 2 = 2
56//! let two_third = two / F::from(3); // 2/3 = 0.666666[...]
57//!
58//! assert_eq!(F::from(2), two);
59//! assert_eq!(F::new(2u64, 3u64), two_third);
60//!
61//! assert_eq!("2/3", format!("{}", two_third)); // print as Fraction (by default)
62//! assert_eq!("0.6666", format!("{:.4}", two_third)); // format as decimal and print up to 4 digits after floating point
63//! ```
64//!
65//! Decimal is implemented as a representation layer on top of Fraction.
66//! Thus, it is also lossless and may require explicit control over "precision"
67//! for comparison and formatting operations.
68//! ```
69//! type D = fraction::Decimal;
70//!
71//! let result = D::from(0.5) / D::from(0.3);
72//!
73//! assert_eq!(format!("{}", result), "1.6"); // calculation result uses precision of the operands
74//! assert_eq!(format!("{:.4}", result), "1.6666"); // explicitly passing precision to format
75//!
76//! assert_eq!("1.6666", format!("{}", result.set_precision(4))); // the other way to set precision explicitly on Decimal
77//! ```
78//!
79//! ## Construct:
80//!
81//! Fraction:
82//!
83//! ```
84//! use std::str::FromStr;
85//! use fraction::{Fraction, Sign};
86//!
87//! // fraction crate also re-exports num::{One, Zero} traits for convenience.
88//! use fraction::{One, Zero};
89//!
90//!
91//! // There are several ways to construct a fraction, depending on your use case
92//!
93//! // `new` - construct with numerator/denominator and normalize the fraction.
94//! // "Normalization" means it will always find the least common denominator
95//! // and convert the input accordingly.
96//! let f = Fraction::new(1u8, 2u8);
97//!
98//! // `new_generic` - construct with numerator/denominator of different integer types
99//! assert_eq!(f, Fraction::new_generic(Sign::Plus, 1i32, 2u8).unwrap());
100//!
101//! // `from` - converts from primitive types such as i32 and f32.
102//! assert_eq!(f, Fraction::from(0.5)); // convert from float (f32, f64)
103//!
104//! // `from_str` - tries parse a string fraction. Supports the usual decimal notation.
105//! assert_eq!(f, Fraction::from_str("0.5").unwrap()); // parse a string
106//!
107//! // `from_str` - also supports _fraction_ notation such as "numerator/denominator" delimited by slash (`/`).
108//! assert_eq!(f, Fraction::from_str("1/2").unwrap()); // parse a string
109//! assert_eq!(Fraction::from_str("1/0").unwrap(), Fraction::infinity());
110//! assert_eq!(Fraction::from_str("-1/0").unwrap(), Fraction::neg_infinity());
111//! assert_eq!(Fraction::from_str("0/0").unwrap(), Fraction::nan());
112//!
113//! // `new_raw` - construct with numerator/denominator but do not normalize the fraction.
114//! // This is the most performant constructor, but does not calculate the common denominator,
115//! // so may lead to unexpected results in following calculations if the fraction is not normalised.
116//! // WARNING: Only use if you are sure numerator/denominator are already normalized.
117//! assert_eq!(f, Fraction::new_raw(1u64, 2u64));
118//!
119//! // `one` - implements num::One trait
120//! assert_eq!(f * 2, Fraction::one());
121//!
122//! // `zero` - implements num::Zero trait
123//! assert_eq!(f - f, Fraction::zero());
124//! ```
125//!
126//! For numeric-component forms, ordinary `Fraction`/`Decimal`, Unicode, and
127//! [`GenericFraction::from_str_radix`] parsing accept at most one optional sign at the beginning
128//! of the complete value. Numeric components are unsigned, so forms such as `1/-2`, `1/+2`, and
129//! `1.-2` are rejected. Fraction Juniper input retains its required explicit leading sign and
130//! rejects component signs. Decimal Juniper input delegates to ordinary Decimal grammar and also
131//! rejects component signs. Ordinary `Fraction` and `Decimal` parsing, together with Unicode
132//! fraction parsing, map zero denominators to infinity or NaN. [`GenericFraction::from_str_radix`]
133//! accepts bases 2 through 36, returns [`error::ParseError::UnsupportedBase`] for other bases,
134//! and returns [`error::ParseError::ZeroDenominator`] for a zero denominator. Decimal radix
135//! parsing is base-10-only and returns `UnsupportedBase` for other bases.
136//!
137//! Decimal:
138//! ```
139//! use std::str::FromStr;
140//! use fraction::{Decimal, Fraction};
141//!
142//! // There are similar ways to construct Decimal. Underneath it is always represented as Fraction.
143//! // When constructed, Decimal preserves its precision (number of digits after floating point).
144//! // When two decimals are calculated, the result takes the biggest precision of both.
145//! // The precision is used for visual representation (formatting and printing) and for comparison of two decimals.
146//! // Precision is NOT used in any calculations. All calculations are lossless and implemented through Fraction.
147//! // To override the precision use Decimal::set_precision.
148//!
149//! let d = Decimal::from(1); // from integer, precision = 0
150//! assert_eq!(d, Decimal::from_fraction(Fraction::from(1))); // from fraction, precision is calculated from fraction
151//!
152//! let d = Decimal::from(1.3); // from float (f32, f64)
153//! assert_eq!(d, Decimal::from_str("1.3").unwrap());
154//!
155//! let d = Decimal::from(0.5); // from float (f32, f64)
156//! assert_eq!(d, Decimal::from_str("1/2").unwrap());
157//! // Decimal fraction notation inherits this same zero-denominator behaviour from Fraction internals.
158//! assert_eq!(Decimal::from_str("1/0").unwrap(), Decimal::infinity());
159//! assert_eq!(Decimal::from_str("-1/0").unwrap(), Decimal::neg_infinity());
160//! assert_eq!(Decimal::from_str("0/0").unwrap(), Decimal::nan());
161//! ```
162//!
163//! ## Format (convert to string)
164//! Formatting works similar for both Decimal and Fraction (Decimal uses Fraction internally).
165//! The format implementation closely follows the rust Format trait documentation.
166//!
167//! ```
168//! type F = fraction::Fraction;
169//!
170//! let result = F::from(0.7) / F::from(0.4);
171//! assert_eq!(format!("{}", result), "7/4"); // Printed as fraction by default
172//! assert_eq!(format!("{:.2}", result), "1.75"); // if precision is defined, printed as decimal
173//! assert_eq!(format!("{:#.3}", result), "1.750"); // to print leading zeroes, pass hash to the format
174//! ```
175//!
176//! Additionally, when the `with-unicode` feature is enabled, there are methods available for various unicode display options:
177//! See [this SO answer](https://stackoverflow.com/a/77861320/14681457) for a discussion.
178//!
179//! ```rust
180//! # #[cfg(feature = "with-unicode")]
181//! # {
182//! type F = fraction::Fraction;
183//!
184//! let res = F::from(0.7) / F::from(0.4);
185//! assert_eq!("7⁄4",format!("{}", res.get_unicode_display())); // needs font support. Unicode way
186//! assert_eq!("13⁄4",format!("{}", res.get_unicode_display().mixed())); // interpreted wrongly without font support
187//! assert_eq!("⁷/₄",format!("{}", res.get_unicode_display().supsub())); // no need for font support
188//! assert_eq!("1³/₄",format!("{}", res.get_unicode_display().supsub().mixed()));
189//! # }
190//! ```
191//!
192//! ## Convert into/from other types
193//!
194//! Both `fraction` and `decimal` types implement
195//! - `from` and `try_into` for all built-in primitive types.
196//! - `from` and `try_into` for `BigInt` and `BigUint` when `with-bigint` feature enabled.
197//!
198//! ```rust
199//! use fraction::{Fraction, One, BigInt, BigUint};
200//! use std::convert::TryInto;
201//!
202//! // Convert from examples (from primitives always succeed)
203//! assert_eq!(Fraction::from(1i8), Fraction::one());
204//! assert_eq!(Fraction::from(1u8), Fraction::one());
205//! assert_eq!(Fraction::from(BigInt::one()), Fraction::one());
206//! assert_eq!(Fraction::from(BigUint::one()), Fraction::one());
207//! assert_eq!(Fraction::from(1f32), Fraction::one());
208//! assert_eq!(Fraction::from(1f64), Fraction::one());
209//!
210//!
211//! // Convert into examples (try_into returns Result<T, ()>)
212//! assert_eq!(Ok(1i8), Fraction::one().try_into());
213//! assert_eq!(Ok(1u8), Fraction::one().try_into());
214//! assert_eq!(Ok(BigInt::one()), Fraction::one().try_into());
215//! assert_eq!(Ok(BigUint::one()), Fraction::one().try_into());
216//! assert_eq!(Ok(1f32), Fraction::one().try_into());
217//! assert_eq!(Ok(1f64), Fraction::one().try_into());
218//! ```
219//!
220//! ### Postgres usage
221//! Postgres uses i16 for its binary protocol, so you'll have to use at least u16
222//! as the base type for fractions/decimals.
223//! Otherwise you may workaround with DynaInt<u8, _something_more_than_u8_>.
224//! The safest way to go with would be DynaInt based types
225//! such as DynaFraction or DynaDecimal as they would prevent
226//! stack overflows for high values.
227//!
228//! Beware bad numbers such as 1/3, 1/7.
229//! Fraction keeps the highest achievable precision (up to 16383 digits after floating point).
230//! Decimal uses its own precision.
231//! So, if you may end up with bad numbers, it may be preferable to go with Decimals over Fractions.
232//!
233//! Infinity is encoded as PostgreSQL numeric infinity markers (`0xD000` and `0xF000`) and can
234//! be used only with PostgreSQL 14+ numeric columns. Older PostgreSQL versions, and constrained
235//! `NUMERIC` columns with fixed precision/scale, do not accept infinity values.
236//!
237//! Both types (fractions and decimals) should work transparently
238//! in accordance with Postgres crate documentation
239
240extern crate num;
241
242#[cfg(feature = "with-bigint")]
243pub use num::bigint::{BigInt, BigUint};
244
245/// The upstream error returned by [`Ratio::from_str_radix`] and the `FromStr`
246/// implementation for [`Ratio`].
247///
248/// [`GenericFraction::from_str_radix`] uses this crate's [`error::ParseError`]
249/// instead, including its explicit zero-denominator and unsupported-base errors.
250pub use num::rational::ParseRatioError;
251pub use num::rational::Ratio;
252
253pub use num::{
254 traits::{ConstOne, ConstZero},
255 Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, Integer, Num, One,
256 Signed, ToPrimitive, Zero,
257};
258
259#[cfg(test)]
260#[macro_use]
261mod tests;
262
263pub mod convert;
264
265pub mod division;
266
267pub mod error;
268
269mod fraction;
270pub use fraction::*;
271
272pub mod generic;
273
274pub mod prelude;
275pub use self::prelude::*;
276
277// ====================================== FEATURES ======================================
278
279#[cfg(feature = "with-juniper-support")]
280extern crate juniper;
281#[cfg(feature = "with-postgres-support")]
282#[macro_use]
283extern crate postgres_types;
284
285#[cfg(feature = "with-serde-support")]
286#[macro_use]
287extern crate serde_derive;
288#[cfg(feature = "with-serde-support")]
289extern crate serde;
290
291#[cfg(feature = "with-decimal")]
292mod decimal;
293#[cfg(feature = "with-dynaint")]
294pub mod dynaint;