pub struct Decimal32<const PRECISION: u32>(/* private fields */);Expand description
§Decimal32
This is a transparent wrapper over an i32. A const generic declares the number of places after the decimal point.
The motivation for such a type is providing lossless arithmetic guarantees like in the example below.
use high_roller::decimal::D9;
use num_traits::{CheckedAdd, WrappingAdd, WrappingSub};
const SMALL: f64 = 0.111000111;
const LARGE: f64 = 2.147483647;
const CHECKED_SMALL: D9 = D9::checked(SMALL).unwrap();
const CHECKED_LARGE: D9 = D9::checked(LARGE).unwrap();
// Parity with lossless operations
let sum = const { D9::checked(1.).unwrap() }.checked_add(&CHECKED_SMALL);
assert_eq!(sum.unwrap().get(), 1. + SMALL, "Result fits in f64");
// Checked operations prevent overflow
let lossy = CHECKED_LARGE.checked_add(&CHECKED_SMALL);
assert_eq!(lossy, None, "Result overflows i32");
assert_ne!(LARGE + SMALL - LARGE, SMALL);
// Wrapping operations enable loss recovery
let wrapped = CHECKED_LARGE.wrapping_add(&CHECKED_SMALL);
assert_eq!(wrapped.wrapping_sub(&CHECKED_LARGE), CHECKED_SMALL);§Design
There are different ways to represent a floating point number with wrapping and saturating semantics. This design basically takes the bounds of an i32 and sticks a decimal point somewhere. So the type itself serves primarily for self-documentation and convenience.
IEEE 754 floating point debauchery keeps the lossless
range of an f32 in signed 2^24.
So an equally valid design could lock the inner value within
that range and use that bound for wrapping, saturating, and
checked operations.
The benefit is that get could return an f32 without loss
of precision. The cost is hardware support for arithmetic.
Since wrapping arithmetic is the primary motivation for
Decimal32, this was not chosen.
A Decimal64 type is not (yet) exposed because at that point, the Decimal crate might be a better fit for your use case.
Implementations§
Source§impl<const PRECISION: u32> Decimal32<PRECISION>
impl<const PRECISION: u32> Decimal32<PRECISION>
pub const ZERO: Self
Sourcepub const fn cast(value: f64) -> Self
pub const fn cast(value: f64) -> Self
Constructor that accepts any input. Truncates toward zero when
the input has more decimal places than PRECISION. Use Self::checked
to detect when precision is lost.
This function takes an f64 to prevent sneaky loss of precision.
f32 only has a 24-bit mantissa, so values between 2^24 and
and 2^31 require an f64 to be constructed.
use high_roller::decimal::D2;
let num = D2::cast(0.125_f64);
assert_eq!(num.get(), 0.12_f64);If this f64 situation is annoying for your use case,
you can still escape it entirely at compile time.
use high_roller::decimal::D3;
const MY_F32: f32 = D3::cast(0.321_f64).get() as f32;
assert_eq!(MY_F32, 0.321);Sourcepub const fn checked(value: f64) -> Option<Self>
pub const fn checked(value: f64) -> Option<Self>
Const constructor that prevents loss of precision from the input value.
§Succeeds
use high_roller::decimal::D5;
const GOOD: D5 = D5::checked(-100.12345).unwrap();§Fails
use high_roller::decimal::D9;
const BAD: D9 = D9::checked(-100.)
.expect("There isn't space in 32 bits for 9 decimal places after -100");Sourcepub const fn get(self) -> f64
pub const fn get(self) -> f64
Returns the inner value as an f64. This conversion is lossless because f64’s 53-bit mantissa can represent every i32 value exactly.
For f32 output use f32::try_from, which returns Err(Lossy) when
the inner value exceeds f32’s 24-bit mantissa.
use high_roller::decimal::D1;
assert_eq!(D1::cast(1.0_f64).get(), 1.0_f64);Trait Implementations§
Source§impl<const P: u32> CheckedAdd for Decimal32<P>
impl<const P: u32> CheckedAdd for Decimal32<P>
Source§fn checked_add(&self, v: &Self) -> Option<Self>
fn checked_add(&self, v: &Self) -> Option<Self>
None is
returned.Source§impl<const P: u32> CheckedSub for Decimal32<P>
impl<const P: u32> CheckedSub for Decimal32<P>
Source§fn checked_sub(&self, v: &Self) -> Option<Self>
fn checked_sub(&self, v: &Self) -> Option<Self>
None is returned.Source§impl<const PRECISION: u32> Ord for Decimal32<PRECISION>
impl<const PRECISION: u32> Ord for Decimal32<PRECISION>
1.21.0 · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl<const PRECISION: u32> PartialOrd for Decimal32<PRECISION>
impl<const PRECISION: u32> PartialOrd for Decimal32<PRECISION>
Source§impl<const P: u32> TryFrom<Decimal32<P>> for f32
impl<const P: u32> TryFrom<Decimal32<P>> for f32
Source§fn try_from(value: Decimal32<P>) -> Result<Self, Self::Error>
fn try_from(value: Decimal32<P>) -> Result<Self, Self::Error>
Converts to f32. Returns Err(Lossy) when the inner value exceeds
f32’s 24-bit mantissa. Use Decimal32::get if unchecked lossless
output is required.
Source§type Error = DecimalErr
type Error = DecimalErr
Source§impl<const P: u32> TryFrom<f32> for Decimal32<P>
impl<const P: u32> TryFrom<f32> for Decimal32<P>
Source§fn try_from(value: f32) -> Result<Self, Self::Error>
fn try_from(value: f32) -> Result<Self, Self::Error>
Constructs a Decimal32 from an f32 or returns Err(DecimalErr::Lossy)
if the conversion would lose precision. This might occur if the input
literal specifies more decimal places than the underlyingDecimal32 type.
use high_roller::decimal::D2;
D2::try_from(0.123).expect("resulting decimal is 0.12");But take care not to lose precision when constructing the f32 input into this function. Since f32 has a 24-bit mantissa, it cannot represent some values that Decimal32 can.
In the example below, rustc abbreviates the float before this
function even sees it. Decimal32::cast solves this case by
using an f64 constructor.
use high_roller::decimal::D9;
const INPUT: f32 = 2.147483647;
let expected = D9::try_from(INPUT).unwrap();
assert_eq!(INPUT, f32::try_from(expected).unwrap());
assert_eq!(INPUT, 2.1474836, "two places were dropped");Source§type Error = DecimalErr
type Error = DecimalErr
Source§impl<const P: u32> WrappingAdd for Decimal32<P>
impl<const P: u32> WrappingAdd for Decimal32<P>
Source§fn wrapping_add(&self, v: &Self) -> Self
fn wrapping_add(&self, v: &Self) -> Self
self + other, wrapping around at the boundary of
the type.Source§impl<const P: u32> WrappingSub for Decimal32<P>
impl<const P: u32> WrappingSub for Decimal32<P>
Source§fn wrapping_sub(&self, v: &Self) -> Self
fn wrapping_sub(&self, v: &Self) -> Self
self - other, wrapping around at the boundary
of the type.