Skip to content

Commit 86266d2

Browse files
committed
Merge rust-bitcoin#4161: Improve ops for amount types
0a9f14f Implement Div by amount for amount types (Tobin C. Harding) b57bfb9 Add missing Mul impls for amount types (Tobin C. Harding) 501c9ab Test amount ops that involve an integer (Tobin C. Harding) 851080d Add more add/sub tests (Tobin C. Harding) 4792395 Improve add/sub tests for amount types (Tobin C. Harding) 8bb9ce3 Add tests for amount op int (Tobin C. Harding) Pull request description: Improve the test coverage and add missing implementations of math operations for the amount types. Along the way close rust-bitcoin#4030. ACKs for top commit: apoelstra: ACK 0a9f14f; successfully ran local tests; nice! Tree-SHA512: f303b2a90b5bb9e77091e047f8325821a5c89f52dfe242d849968dba0d097d3868d444009c2c05b9d7c0e91fa2ce6898cdc4733977699ca4b1ae226562878cdf
2 parents eee3505 + 0a9f14f commit 86266d2

File tree

2 files changed

+202
-36
lines changed

2 files changed

+202
-36
lines changed

units/src/amount/result.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ crate::internal_macros::impl_op_for_references! {
161161

162162
fn mul(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs * rhs) }
163163
}
164+
impl ops::Mul<Amount> for u64 {
165+
type Output = NumOpResult<Amount>;
166+
167+
fn mul(self, rhs: Amount) -> Self::Output { rhs.checked_mul(self).valid_or_error() }
168+
}
169+
impl ops::Mul<NumOpResult<Amount>> for u64 {
170+
type Output = NumOpResult<Amount>;
171+
172+
fn mul(self, rhs: NumOpResult<Amount>) -> Self::Output { rhs.and_then(|rhs| self * rhs) }
173+
}
164174

165175
impl ops::Div<u64> for Amount {
166176
type Output = NumOpResult<Amount>;
@@ -172,6 +182,11 @@ crate::internal_macros::impl_op_for_references! {
172182

173183
fn div(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs / rhs) }
174184
}
185+
impl ops::Div<Amount> for Amount {
186+
type Output = u64;
187+
188+
fn div(self, rhs: Amount) -> Self::Output { self.to_sat() / rhs.to_sat() }
189+
}
175190

176191
impl ops::Rem<u64> for Amount {
177192
type Output = NumOpResult<Amount>;
@@ -221,6 +236,16 @@ crate::internal_macros::impl_op_for_references! {
221236

222237
fn mul(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs * rhs) }
223238
}
239+
impl ops::Mul<SignedAmount> for i64 {
240+
type Output = NumOpResult<SignedAmount>;
241+
242+
fn mul(self, rhs: SignedAmount) -> Self::Output { rhs.checked_mul(self).valid_or_error() }
243+
}
244+
impl ops::Mul<NumOpResult<SignedAmount>> for i64 {
245+
type Output = NumOpResult<SignedAmount>;
246+
247+
fn mul(self, rhs: NumOpResult<SignedAmount>) -> Self::Output { rhs.and_then(|rhs| self * rhs) }
248+
}
224249

225250
impl ops::Div<i64> for SignedAmount {
226251
type Output = NumOpResult<SignedAmount>;
@@ -232,6 +257,11 @@ crate::internal_macros::impl_op_for_references! {
232257

233258
fn div(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs / rhs) }
234259
}
260+
impl ops::Div<SignedAmount> for SignedAmount {
261+
type Output = i64;
262+
263+
fn div(self, rhs: SignedAmount) -> Self::Output { self.to_sat() / rhs.to_sat() }
264+
}
235265

236266
impl ops::Rem<i64> for SignedAmount {
237267
type Output = NumOpResult<SignedAmount>;

units/src/amount/tests.rs

Lines changed: 172 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,64 +1138,191 @@ fn trailing_zeros_for_amount() {
11381138
}
11391139

11401140
#[test]
1141-
#[allow(clippy::op_ref)]
1141+
fn add_sub_combos() {
1142+
// Checks lhs op rhs for all reference combos.
1143+
macro_rules! check_ref {
1144+
($($lhs:ident $op:tt $rhs:ident = $ans:ident);* $(;)?) => {
1145+
$(
1146+
assert_eq!($lhs $op $rhs, $ans);
1147+
assert_eq!(&$lhs $op $rhs, $ans);
1148+
assert_eq!($lhs $op &$rhs, $ans);
1149+
assert_eq!(&$lhs $op &$rhs, $ans);
1150+
)*
1151+
}
1152+
}
1153+
1154+
// Checks lhs op rhs for all amount and `NumOpResult` combos.
1155+
macro_rules! check_res {
1156+
($($amount:ident, $op:tt, $lhs:literal, $rhs:literal, $ans:literal);* $(;)?) => {
1157+
$(
1158+
let amt = |sat| $amount::from_sat(sat);
1159+
1160+
let sat_lhs = amt($lhs);
1161+
let sat_rhs = amt($rhs);
1162+
1163+
let res_lhs = NumOpResult::from(sat_lhs);
1164+
let res_rhs = NumOpResult::from(sat_rhs);
1165+
1166+
let ans = NumOpResult::from(amt($ans));
1167+
1168+
check_ref! {
1169+
sat_lhs $op sat_rhs = ans;
1170+
sat_lhs $op res_rhs = ans;
1171+
res_lhs $op sat_rhs = ans;
1172+
res_lhs $op res_rhs = ans;
1173+
}
1174+
)*
1175+
}
1176+
}
1177+
1178+
// Checks lhs op rhs for both amount types.
1179+
macro_rules! check_op {
1180+
($($lhs:literal $op:tt $rhs:literal = $ans:literal);* $(;)?) => {
1181+
$(
1182+
check_res!(Amount, $op, $lhs, $rhs, $ans);
1183+
check_res!(SignedAmount, $op, $lhs, $rhs, $ans);
1184+
)*
1185+
}
1186+
}
1187+
1188+
// We do not currently support division involving `NumOpResult` and an amount type.
1189+
check_op! {
1190+
307 + 461 = 768;
1191+
461 - 307 = 154;
1192+
}
1193+
}
1194+
1195+
#[test]
11421196
fn unsigned_addition() {
11431197
let sat = Amount::from_sat;
11441198

1145-
let one = sat(1);
1146-
let two = sat(2);
1147-
let three = sat(3);
1199+
assert_eq!(sat(0) + sat(0), NumOpResult::from(sat(0)));
1200+
assert_eq!(sat(0) + sat(307), NumOpResult::from(sat(307)));
1201+
assert_eq!(sat(307) + sat(0), NumOpResult::from(sat(307)));
1202+
assert_eq!(sat(307) + sat(461), NumOpResult::from(sat(768)));
1203+
assert_eq!(sat(0) + Amount::MAX_MONEY, NumOpResult::from(Amount::MAX_MONEY));
1204+
}
11481205

1149-
assert!((one + two) == three.into());
1150-
assert!((one + two) == three.into());
1151-
assert!((&one + two) == three.into());
1152-
assert!((one + &two) == three.into());
1153-
assert!((&one + &two) == three.into());
1206+
#[test]
1207+
fn signed_addition() {
1208+
let ssat = SignedAmount::from_sat;
1209+
1210+
assert_eq!(ssat(0) + ssat(0), NumOpResult::from(ssat(0)));
1211+
assert_eq!(ssat(0) + ssat(307), NumOpResult::from(ssat(307)));
1212+
assert_eq!(ssat(307) + ssat(0), NumOpResult::from(ssat(307)));
1213+
assert_eq!(ssat(307) + ssat(461), NumOpResult::from(ssat(768)));
1214+
assert_eq!(ssat(0) + SignedAmount::MAX_MONEY, NumOpResult::from(SignedAmount::MAX_MONEY));
1215+
1216+
assert_eq!(ssat(0) + ssat(-307), NumOpResult::from(ssat(-307)));
1217+
assert_eq!(ssat(-307) + ssat(0), NumOpResult::from(ssat(-307)));
1218+
assert_eq!(ssat(-307) + ssat(461), NumOpResult::from(ssat(154)));
1219+
assert_eq!(ssat(307) + ssat(-461), NumOpResult::from(ssat(-154)));
1220+
assert_eq!(ssat(-307) + ssat(-461), NumOpResult::from(ssat(-768)));
1221+
assert_eq!(
1222+
SignedAmount::MAX_MONEY + -SignedAmount::MAX_MONEY,
1223+
NumOpResult::from(SignedAmount::ZERO)
1224+
);
11541225
}
11551226

11561227
#[test]
1157-
#[allow(clippy::op_ref)]
1158-
fn unsigned_subtract() {
1228+
fn unsigned_subtraction() {
11591229
let sat = Amount::from_sat;
11601230

1161-
let one = sat(1);
1162-
let two = sat(2);
1163-
let three = sat(3);
1231+
assert_eq!(sat(0) - sat(0), NumOpResult::from(sat(0)));
1232+
assert_eq!(sat(307) - sat(0), NumOpResult::from(sat(307)));
1233+
assert_eq!(sat(461) - sat(307), NumOpResult::from(sat(154)));
1234+
}
1235+
1236+
#[test]
1237+
fn signed_subtraction() {
1238+
let ssat = SignedAmount::from_sat;
11641239

1165-
assert!(three - two == one.into());
1166-
assert!(&three - two == one.into());
1167-
assert!(three - &two == one.into());
1168-
assert!(&three - &two == one.into());
1240+
assert_eq!(ssat(0) - ssat(0), NumOpResult::from(ssat(0)));
1241+
assert_eq!(ssat(0) - ssat(307), NumOpResult::from(ssat(-307)));
1242+
assert_eq!(ssat(307) - ssat(0), NumOpResult::from(ssat(307)));
1243+
assert_eq!(ssat(307) - ssat(461), NumOpResult::from(ssat(-154)));
1244+
assert_eq!(ssat(0) - SignedAmount::MAX_MONEY, NumOpResult::from(-SignedAmount::MAX_MONEY));
1245+
1246+
assert_eq!(ssat(0) - ssat(-307), NumOpResult::from(ssat(307)));
1247+
assert_eq!(ssat(-307) - ssat(0), NumOpResult::from(ssat(-307)));
1248+
assert_eq!(ssat(-307) - ssat(461), NumOpResult::from(ssat(-768)));
1249+
assert_eq!(ssat(307) - ssat(-461), NumOpResult::from(ssat(768)));
1250+
assert_eq!(ssat(-307) - ssat(-461), NumOpResult::from(ssat(154)));
11691251
}
11701252

11711253
#[test]
1172-
#[allow(clippy::op_ref)]
1173-
fn signed_addition() {
1254+
fn op_int_combos() {
1255+
let sat = Amount::from_sat;
11741256
let ssat = SignedAmount::from_sat;
11751257

1176-
let one = ssat(1);
1177-
let two = ssat(2);
1178-
let three = ssat(3);
1258+
let res = |sat| NumOpResult::from(Amount::from_sat(sat));
1259+
let sres = |ssat| NumOpResult::from(SignedAmount::from_sat(ssat));
1260+
1261+
assert_eq!(sat(23) * 31, res(713));
1262+
assert_eq!(ssat(23) * 31, sres(713));
1263+
assert_eq!(res(23) * 31, res(713));
1264+
assert_eq!(sres(23) * 31, sres(713));
1265+
1266+
assert_eq!(31 * sat(23), res(713));
1267+
assert_eq!(31 * ssat(23), sres(713));
1268+
assert_eq!(31 * res(23), res(713));
1269+
assert_eq!(31 * sres(23), sres(713));
1270+
1271+
// No remainder.
1272+
assert_eq!(sat(1897) / 7, res(271));
1273+
assert_eq!(ssat(1897) / 7, sres(271));
1274+
assert_eq!(res(1897) / 7, res(271));
1275+
assert_eq!(sres(1897) / 7, sres(271));
1276+
1277+
// Truncation works as expected.
1278+
assert_eq!(sat(1901) / 7, res(271));
1279+
assert_eq!(ssat(1901) / 7, sres(271));
1280+
assert_eq!(res(1901) / 7, res(271));
1281+
assert_eq!(sres(1901) / 7, sres(271));
1282+
1283+
// No remainder.
1284+
assert_eq!(sat(1897) % 7, res(0));
1285+
assert_eq!(ssat(1897) % 7, sres(0));
1286+
assert_eq!(res(1897) % 7, res(0));
1287+
assert_eq!(sres(1897) % 7, sres(0));
1288+
1289+
// Remainder works as expected.
1290+
assert_eq!(sat(1901) % 7, res(4));
1291+
assert_eq!(ssat(1901) % 7, sres(4));
1292+
assert_eq!(res(1901) % 7, res(4));
1293+
assert_eq!(sres(1901) % 7, sres(4));
1294+
}
1295+
1296+
#[test]
1297+
fn unsigned_amount_div_by_amount() {
1298+
let sat = Amount::from_sat;
1299+
1300+
assert_eq!(sat(0) / sat(7), 0);
1301+
assert_eq!(sat(1897) / sat(7), 271);
1302+
}
11791303

1180-
assert!(one + two == three.into());
1181-
assert!(&one + two == three.into());
1182-
assert!(one + &two == three.into());
1183-
assert!(&one + &two == three.into());
1304+
#[test]
1305+
#[should_panic(expected = "attempt to divide by zero")]
1306+
fn unsigned_amount_div_by_amount_zero() {
1307+
let _ = Amount::from_sat(1897) / Amount::ZERO;
11841308
}
11851309

11861310
#[test]
1187-
#[allow(clippy::op_ref)]
1188-
fn signed_subtract() {
1311+
fn signed_amount_div_by_amount() {
11891312
let ssat = SignedAmount::from_sat;
11901313

1191-
let one = ssat(1);
1192-
let two = ssat(2);
1193-
let three = ssat(3);
1314+
assert_eq!(ssat(0) / ssat(7), 0);
1315+
1316+
assert_eq!(ssat(1897) / ssat(7), 271);
1317+
assert_eq!(ssat(1897) / ssat(-7), -271);
1318+
assert_eq!(ssat(-1897) / ssat(7), -271);
1319+
assert_eq!(ssat(-1897) / ssat(-7), 271);
1320+
}
11941321

1195-
assert!(three - two == one.into());
1196-
assert!(&three - two == one.into());
1197-
assert!(three - &two == one.into());
1198-
assert!(&three - &two == one.into());
1322+
#[test]
1323+
#[should_panic(expected = "attempt to divide by zero")]
1324+
fn signed_amount_div_by_amount_zero() {
1325+
let _ = SignedAmount::from_sat(1897) / SignedAmount::ZERO;
11991326
}
12001327

12011328
#[test]
@@ -1300,6 +1427,15 @@ fn num_op_result_ops_integer() {
13001427
}
13011428
}
13021429
check_op! {
1430+
// Operations on an amount type and an integer.
1431+
let _ = sat * 3_u64; // Explicit type for the benefit of the reader.
1432+
let _ = sat / 3;
1433+
let _ = sat % 3;
1434+
1435+
let _ = ssat * 3_i64; // Explicit type for the benefit of the reader.
1436+
let _ = ssat / 3;
1437+
let _ = ssat % 3;
1438+
13031439
// Operations on a `NumOpResult` and integer.
13041440
let _ = res * 3_u64; // Explicit type for the benefit of the reader.
13051441
let _ = res / 3;

0 commit comments

Comments
 (0)