Skip to content

Commit

Permalink
Add unchecked variant
Browse files Browse the repository at this point in the history
This variant is supplied in cases where a Result type does not need to
be handled.
  • Loading branch information
yancyribbens committed Dec 11, 2024
1 parent 7c8ad08 commit d230ba9
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 59 deletions.
22 changes: 19 additions & 3 deletions units/src/amount/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ impl SignedAmount {
}
}

/// Constructs a new [`SignedAmount`] with satoshi precision and the given number of satoshis.
///
/// # Panics
///
/// On values exceeding [`SignedAmount::MAX`] or less than [`SignedAmount::MIN`]
#[allow(clippy::absurd_extreme_comparisons)]
pub const fn from_sat_unchecked(satoshi: i64) -> SignedAmount {
if satoshi < Self::MIN.0 {
panic!("Out Of Range Error: less than MIN")
} else if satoshi > Self::MAX.0 {
panic!("Out Of Range Error: greater than MAX")
} else {
SignedAmount(satoshi)
}
}

/// Gets the number of satoshis in this [`SignedAmount`].
pub const fn to_sat(self) -> i64 { self.0 }

Expand Down Expand Up @@ -163,7 +179,7 @@ impl SignedAmount {
///
/// ```
/// # use bitcoin_units::amount::{SignedAmount, Denomination};
/// let amount = SignedAmount::from_sat(100_000);
/// let amount = SignedAmount::from_sat_unchecked(100_000);
/// assert_eq!(amount.to_btc(), amount.to_float_in(Denomination::Bitcoin))
/// ```
#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -488,7 +504,7 @@ impl TryFrom<Amount> for SignedAmount {
impl core::iter::Sum for SignedAmount {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let sats: i64 = iter.map(|amt| amt.0).sum();
SignedAmount::from_sat(sats)
SignedAmount::from_sat_unchecked(sats)
}
}

Expand All @@ -498,7 +514,7 @@ impl<'a> core::iter::Sum<&'a SignedAmount> for SignedAmount {
I: Iterator<Item = &'a SignedAmount>,
{
let sats: i64 = iter.map(|amt| amt.0).sum();
SignedAmount::from_sat(sats)
SignedAmount::from_sat_unchecked(sats)
}
}

Expand Down
76 changes: 38 additions & 38 deletions units/src/amount/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn from_str_zero() {
}
match s.parse::<SignedAmount>() {
Err(e) => panic!("failed to crate amount from {}: {:?}", s, e),
Ok(amount) => assert_eq!(amount, SignedAmount::from_sat(0)),
Ok(amount) => assert_eq!(amount, SignedAmount::from_sat_unchecked(0)),
}
}
}
Expand Down Expand Up @@ -79,24 +79,24 @@ fn from_int_btc_panic() { Amount::from_int_btc_const(u64::MAX); }
fn test_signed_amount_try_from_amount() {
let ua_positive = Amount::from_sat(123);
let sa_positive = SignedAmount::try_from(ua_positive).unwrap();
assert_eq!(sa_positive, SignedAmount::from_sat(123));
assert_eq!(sa_positive, SignedAmount::from_sat_unchecked(123));
}

#[test]
fn test_amount_try_from_signed_amount() {
let sa_positive = SignedAmount::from_sat(123);
let sa_positive = SignedAmount::from_sat_unchecked(123);
let ua_positive = Amount::try_from(sa_positive).unwrap();
assert_eq!(ua_positive, Amount::from_sat(123));

let sa_negative = SignedAmount::from_sat(-123);
let sa_negative = SignedAmount::from_sat_unchecked(-123);
let result = Amount::try_from(sa_negative);
assert_eq!(result, Err(OutOfRangeError { is_signed: false, is_greater_than_max: false }));
}

#[test]
fn mul_div() {
let sat = Amount::from_sat;
let ssat = SignedAmount::from_sat;
let ssat = SignedAmount::from_sat_unchecked;

assert_eq!(sat(14) * 3, sat(42));
assert_eq!(sat(14) / 2, sat(7));
Expand Down Expand Up @@ -125,7 +125,7 @@ fn test_overflows() {
#[test]
fn checked_arithmetic() {
let sat = Amount::from_sat;
let ssat = SignedAmount::from_sat;
let ssat = SignedAmount::from_sat_unchecked;

assert_eq!(SignedAmount::MAX.checked_add(ssat(1)), None);
assert_eq!(SignedAmount::MIN.checked_sub(ssat(1)), None);
Expand Down Expand Up @@ -207,7 +207,7 @@ fn floating_point() {
let f = Amount::from_float_in;
let sf = SignedAmount::from_float_in;
let sat = Amount::from_sat;
let ssat = SignedAmount::from_sat;
let ssat = SignedAmount::from_sat_unchecked;

assert_eq!(f(11.22, D::Bitcoin), Ok(sat(1122000000)));
assert_eq!(sf(-11.22, D::MilliBitcoin), Ok(ssat(-1122000)));
Expand Down Expand Up @@ -281,9 +281,9 @@ fn parsing() {
assert_eq!(p("1000.0000001", sat), Err(TooPreciseError { position: 11 }.into()));

assert_eq!(p("1", btc), Ok(Amount::from_sat(1_000_000_00)));
assert_eq!(sp("-.5", btc), Ok(SignedAmount::from_sat(-500_000_00)));
assert_eq!(sp("-.5", btc), Ok(SignedAmount::from_sat_unchecked(-500_000_00)));
#[cfg(feature = "alloc")]
assert_eq!(sp(&i64::MIN.to_string(), sat), Ok(SignedAmount::from_sat(i64::MIN)));
assert_eq!(sp(&i64::MIN.to_string(), sat), Ok(SignedAmount::from_sat_unchecked(i64::MIN)));
assert_eq!(p("1.1", btc), Ok(Amount::from_sat(1_100_000_00)));
assert_eq!(p("100", sat), Ok(Amount::from_sat(100)));
assert_eq!(p("55", sat), Ok(Amount::from_sat(55)));
Expand Down Expand Up @@ -312,13 +312,13 @@ fn to_string() {
assert_eq!(format!("{:.8}", Amount::ONE_BTC.display_in(D::Bitcoin)), "1.00000000");
assert_eq!(Amount::ONE_BTC.to_string_in(D::Satoshi), "100000000");
assert_eq!(Amount::ONE_SAT.to_string_in(D::Bitcoin), "0.00000001");
assert_eq!(SignedAmount::from_sat(-42).to_string_in(D::Bitcoin), "-0.00000042");
assert_eq!(SignedAmount::from_sat_unchecked(-42).to_string_in(D::Bitcoin), "-0.00000042");

assert_eq!(Amount::ONE_BTC.to_string_with_denomination(D::Bitcoin), "1 BTC");
assert_eq!(SignedAmount::ONE_BTC.to_string_with_denomination(D::Satoshi), "100000000 satoshi");
assert_eq!(Amount::ONE_SAT.to_string_with_denomination(D::Bitcoin), "0.00000001 BTC");
assert_eq!(
SignedAmount::from_sat(-42).to_string_with_denomination(D::Bitcoin),
SignedAmount::from_sat_unchecked(-42).to_string_with_denomination(D::Bitcoin),
"-0.00000042 BTC"
);
}
Expand All @@ -343,7 +343,7 @@ macro_rules! check_format_non_negative {
#[cfg(feature = "alloc")]
fn $test_name() {
assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom)), $expected);
assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom)), $expected);
assert_eq!(format!($format_string, SignedAmount::from_sat_unchecked($val as i64).display_in(Denomination::$denom)), $expected);
}
)*
}
Expand All @@ -356,7 +356,7 @@ macro_rules! check_format_non_negative_show_denom {
#[cfg(feature = "alloc")]
fn $test_name() {
assert_eq!(format!($format_string, Amount::from_sat($val).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
assert_eq!(format!($format_string, SignedAmount::from_sat($val as i64).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
assert_eq!(format!($format_string, SignedAmount::from_sat_unchecked($val as i64).display_in(Denomination::$denom).show_denomination()), concat!($expected, $denom_suffix));
}
)*
}
Expand Down Expand Up @@ -497,7 +497,7 @@ check_format_non_negative_show_denom! {

#[test]
fn test_unsigned_signed_conversion() {
let sa = SignedAmount::from_sat;
let sa = SignedAmount::from_sat_unchecked;
let ua = Amount::from_sat;
let max_sats: u64 = Amount::MAX.to_sat();

Expand Down Expand Up @@ -575,13 +575,13 @@ fn from_str() {
case("18446744073709551616 sat", Err(OutOfRangeError::too_big(false)));

ok_case(".5 bits", Amount::from_sat(50));
ok_scase("-.5 bits", SignedAmount::from_sat(-50));
ok_scase("-.5 bits", SignedAmount::from_sat_unchecked(-50));
ok_case("0.00253583 BTC", Amount::from_sat(253583));
ok_scase("-5 satoshi", SignedAmount::from_sat(-5));
ok_scase("-5 satoshi", SignedAmount::from_sat_unchecked(-5));
ok_case("0.10000000 BTC", Amount::from_sat(100_000_00));
ok_scase("-100 bits", SignedAmount::from_sat(-10_000));
ok_scase("-100 bits", SignedAmount::from_sat_unchecked(-10_000));
#[cfg(feature = "alloc")]
ok_scase(&format!("{} SAT", i64::MIN), SignedAmount::from_sat(i64::MIN));
ok_scase(&format!("{} SAT", i64::MIN), SignedAmount::from_sat_unchecked(i64::MIN));
}

#[cfg(feature = "alloc")]
Expand All @@ -592,19 +592,19 @@ fn to_from_string_in() {
let ua_str = Amount::from_str_in;
let ua_sat = Amount::from_sat;
let sa_str = SignedAmount::from_str_in;
let sa_sat = SignedAmount::from_sat;
let sa_sat = SignedAmount::from_sat_unchecked;

assert_eq!("0.5", Amount::from_sat(50).to_string_in(D::Bit));
assert_eq!("-0.5", SignedAmount::from_sat(-50).to_string_in(D::Bit));
assert_eq!("-0.5", SignedAmount::from_sat_unchecked(-50).to_string_in(D::Bit));
assert_eq!("0.00253583", Amount::from_sat(253583).to_string_in(D::Bitcoin));
assert_eq!("-5", SignedAmount::from_sat(-5).to_string_in(D::Satoshi));
assert_eq!("-5", SignedAmount::from_sat_unchecked(-5).to_string_in(D::Satoshi));
assert_eq!("0.1", Amount::from_sat(100_000_00).to_string_in(D::Bitcoin));
assert_eq!("-100", SignedAmount::from_sat(-10_000).to_string_in(D::Bit));
assert_eq!("-100", SignedAmount::from_sat_unchecked(-10_000).to_string_in(D::Bit));

assert_eq!("0.50", format!("{:.2}", Amount::from_sat(50).display_in(D::Bit)));
assert_eq!("-0.50", format!("{:.2}", SignedAmount::from_sat(-50).display_in(D::Bit)));
assert_eq!("-0.50", format!("{:.2}", SignedAmount::from_sat_unchecked(-50).display_in(D::Bit)));
assert_eq!("0.10000000", format!("{:.8}", Amount::from_sat(100_000_00).display_in(D::Bitcoin)));
assert_eq!("-100.00", format!("{:.2}", SignedAmount::from_sat(-10_000).display_in(D::Bit)));
assert_eq!("-100.00", format!("{:.2}", SignedAmount::from_sat_unchecked(-10_000).display_in(D::Bit)));

assert_eq!(ua_str(&ua_sat(0).to_string_in(D::Satoshi), D::Satoshi), Ok(ua_sat(0)));
assert_eq!(ua_str(&ua_sat(500).to_string_in(D::Bitcoin), D::Bitcoin), Ok(ua_sat(500)));
Expand Down Expand Up @@ -666,7 +666,7 @@ fn serde_as_sat() {
}

serde_test::assert_tokens(
&T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat(-123456789) },
&T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat_unchecked(-123456789) },
&[
serde_test::Token::Struct { name: "T", len: 2 },
serde_test::Token::Str("amt"),
Expand Down Expand Up @@ -695,7 +695,7 @@ fn serde_as_btc() {

let orig = T {
amt: Amount::from_sat(20_000_000__000_000_01),
samt: SignedAmount::from_sat(-20_000_000__000_000_01),
samt: SignedAmount::from_sat_unchecked(-20_000_000__000_000_01),
};

let json = "{\"amt\": 20000000.00000001, \
Expand Down Expand Up @@ -730,7 +730,7 @@ fn serde_as_str() {
}

serde_test::assert_tokens(
&T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat(-123456789) },
&T { amt: Amount::from_sat(123456789), samt: SignedAmount::from_sat_unchecked(-123456789) },
&[
serde_test::Token::Struct { name: "T", len: 2 },
serde_test::Token::String("amt"),
Expand Down Expand Up @@ -759,7 +759,7 @@ fn serde_as_btc_opt() {

let with = T {
amt: Some(Amount::from_sat(2_500_000_00)),
samt: Some(SignedAmount::from_sat(-2_500_000_00)),
samt: Some(SignedAmount::from_sat_unchecked(-2_500_000_00)),
};
let without = T { amt: None, samt: None };

Expand Down Expand Up @@ -801,7 +801,7 @@ fn serde_as_sat_opt() {

let with = T {
amt: Some(Amount::from_sat(2_500_000_00)),
samt: Some(SignedAmount::from_sat(-2_500_000_00)),
samt: Some(SignedAmount::from_sat_unchecked(-2_500_000_00)),
};
let without = T { amt: None, samt: None };

Expand Down Expand Up @@ -843,7 +843,7 @@ fn serde_as_str_opt() {

let with = T {
amt: Some(Amount::from_sat(123456789)),
samt: Some(SignedAmount::from_sat(-123456789)),
samt: Some(SignedAmount::from_sat_unchecked(-123456789)),
};
let without = T { amt: None, samt: None };

Expand Down Expand Up @@ -872,22 +872,22 @@ fn serde_as_str_opt() {
#[test]
fn sum_amounts() {
assert_eq!(Amount::from_sat(0), [].iter().sum::<Amount>());
assert_eq!(SignedAmount::from_sat(0), [].iter().sum::<SignedAmount>());
assert_eq!(SignedAmount::from_sat_unchecked(0), [].iter().sum::<SignedAmount>());

let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
let sum = amounts.into_iter().sum::<Amount>();
assert_eq!(Amount::from_sat(1400), sum);

let amounts =
[SignedAmount::from_sat(-42), SignedAmount::from_sat(1337), SignedAmount::from_sat(21)];
[SignedAmount::from_sat_unchecked(-42), SignedAmount::from_sat_unchecked(1337), SignedAmount::from_sat_unchecked(21)];
let sum = amounts.into_iter().sum::<SignedAmount>();
assert_eq!(SignedAmount::from_sat(1316), sum);
assert_eq!(SignedAmount::from_sat_unchecked(1316), sum);
}

#[test]
fn checked_sum_amounts() {
assert_eq!(Some(Amount::from_sat(0)), [].into_iter().checked_sum());
assert_eq!(Some(SignedAmount::from_sat(0)), [].into_iter().checked_sum());
assert_eq!(Some(SignedAmount::from_sat_unchecked(0)), [].into_iter().checked_sum());

let amounts = [Amount::from_sat(42), Amount::from_sat(1337), Amount::from_sat(21)];
let sum = amounts.into_iter().checked_sum();
Expand All @@ -898,19 +898,19 @@ fn checked_sum_amounts() {
assert_eq!(None, sum);

let amounts =
[SignedAmount::from_sat(i64::MIN), SignedAmount::from_sat(-1), SignedAmount::from_sat(21)];
[SignedAmount::from_sat_unchecked(i64::MIN), SignedAmount::from_sat_unchecked(-1), SignedAmount::from_sat_unchecked(21)];
let sum = amounts.into_iter().checked_sum();
assert_eq!(None, sum);

let amounts =
[SignedAmount::from_sat(i64::MAX), SignedAmount::from_sat(1), SignedAmount::from_sat(21)];
[SignedAmount::from_sat_unchecked(i64::MAX), SignedAmount::from_sat_unchecked(1), SignedAmount::from_sat_unchecked(21)];
let sum = amounts.into_iter().checked_sum();
assert_eq!(None, sum);

let amounts =
[SignedAmount::from_sat(42), SignedAmount::from_sat(3301), SignedAmount::from_sat(21)];
[SignedAmount::from_sat_unchecked(42), SignedAmount::from_sat_unchecked(3301), SignedAmount::from_sat_unchecked(21)];
let sum = amounts.into_iter().checked_sum();
assert_eq!(Some(SignedAmount::from_sat(3364)), sum);
assert_eq!(Some(SignedAmount::from_sat_unchecked(3364)), sum);
}

#[test]
Expand Down
36 changes: 18 additions & 18 deletions units/src/amount/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn u_amount_homomorphic() {
assert_eq!(
Amount::from_sat(n1).to_signed(),
if n1 <= i64::MAX as u64 {
Ok(SignedAmount::from_sat(n1.try_into().unwrap()))
Ok(SignedAmount::from_sat_unchecked(n1.try_into().unwrap()))
} else {
Err(OutOfRangeError::too_big(true))
},
Expand Down Expand Up @@ -71,23 +71,23 @@ fn s_amount_homomorphic() {
kani::assume(n1.checked_add(n2).is_some()); // assume we don't overflow in the actual test
kani::assume(n1.checked_sub(n2).is_some()); // assume we don't overflow in the actual test
assert_eq!(
SignedAmount::from_sat(n1) + SignedAmount::from_sat(n2),
SignedAmount::from_sat(n1 + n2)
SignedAmount::from_sat_unchecked(n1) + SignedAmount::from_sat_unchecked(n2),
SignedAmount::from_sat_unchecked(n1 + n2)
);
assert_eq!(
SignedAmount::from_sat(n1) - SignedAmount::from_sat(n2),
SignedAmount::from_sat(n1 - n2)
SignedAmount::from_sat_unchecked(n1) - SignedAmount::from_sat_unchecked(n2),
SignedAmount::from_sat_unchecked(n1 - n2)
);

let mut amt = SignedAmount::from_sat(n1);
amt += SignedAmount::from_sat(n2);
assert_eq!(amt, SignedAmount::from_sat(n1 + n2));
let mut amt = SignedAmount::from_sat(n1);
amt -= SignedAmount::from_sat(n2);
assert_eq!(amt, SignedAmount::from_sat(n1 - n2));
let mut amt = SignedAmount::from_sat_unchecked(n1);
amt += SignedAmount::from_sat_unchecked(n2);
assert_eq!(amt, SignedAmount::from_sat_unchecked(n1 + n2));
let mut amt = SignedAmount::from_sat_unchecked(n1);
amt -= SignedAmount::from_sat_unchecked(n2);
assert_eq!(amt, SignedAmount::from_sat_unchecked(n1 - n2));

assert_eq!(
SignedAmount::from_sat(n1).to_unsigned(),
SignedAmount::from_sat_unchecked(n1).to_unsigned(),
if n1 >= 0 {
Ok(Amount::from_sat(n1.try_into().unwrap()))
} else {
Expand All @@ -102,16 +102,16 @@ fn s_amount_homomorphic_checked() {
let n1 = kani::any::<i64>();
let n2 = kani::any::<i64>();
assert_eq!(
SignedAmount::from_sat(n1).checked_add(SignedAmount::from_sat(n2)),
n1.checked_add(n2).map(SignedAmount::from_sat),
SignedAmount::from_sat_unchecked(n1).checked_add(SignedAmount::from_sat_unchecked(n2)),
n1.checked_add(n2).map(SignedAmount::from_sat_unchecked),
);
assert_eq!(
SignedAmount::from_sat(n1).checked_sub(SignedAmount::from_sat(n2)),
n1.checked_sub(n2).map(SignedAmount::from_sat),
SignedAmount::from_sat_unchecked(n1).checked_sub(SignedAmount::from_sat_unchecked(n2)),
n1.checked_sub(n2).map(SignedAmount::from_sat_unchecked),
);

assert_eq!(
SignedAmount::from_sat(n1).positive_sub(SignedAmount::from_sat(n2)),
if n1 >= 0 && n2 >= 0 && n1 >= n2 { Some(SignedAmount::from_sat(n1 - n2)) } else { None },
SignedAmount::from_sat_unchecked(n1).positive_sub(SignedAmount::from_sat_unchecked(n2)),
if n1 >= 0 && n2 >= 0 && n1 >= n2 { Some(SignedAmount::from_sat_unchecked(n1 - n2)) } else { None },
);
}

0 comments on commit d230ba9

Please sign in to comment.