diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index a8cbcbe0..208f0fe2 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -135,7 +135,6 @@ where pub struct Float(FixedBytes<32>); impl Float { - #[allow(dead_code)] // will be used in future tests #[cfg(test)] fn pack_lossless(coefficient: I224, exponent: i32) -> Result { let calldata = DecimalFloat::packLosslessCall { @@ -150,6 +149,27 @@ impl Float { }) } + #[cfg(test)] + fn unpack(self) -> Result<(I224, i32), FloatError> { + let Float(float) = self; + let calldata = DecimalFloat::unpackCall { float }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let DecimalFloat::unpackReturn { + _0: coefficient, + _1: exponent, + } = DecimalFloat::unpackCall::abi_decode_returns(output.as_ref())?; + + Ok((coefficient, exponent)) + }) + } + + #[cfg(test)] + fn show_unpacked(self) -> Result { + let (coefficient, exponent) = self.unpack()?; + Ok(format!("{coefficient}e{exponent}")) + } + pub fn parse(str: String) -> Result { let calldata = DecimalFloat::parseCall { str }.abi_encode(); @@ -177,6 +197,39 @@ impl Float { Ok(decoded) }) } + + pub fn lt(self, b: Self) -> Result { + let Float(a) = self; + let Float(b) = b; + let calldata = DecimalFloat::ltCall { a, b }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::ltCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) + }) + } + + pub fn eq(self, b: Self) -> Result { + let Float(a) = self; + let Float(b) = b; + let calldata = DecimalFloat::eqCall { a, b }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::eqCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) + }) + } + + pub fn gt(self, b: Self) -> Result { + let Float(a) = self; + let Float(b) = b; + let calldata = DecimalFloat::gtCall { a, b }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::gtCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) + }) + } } impl Add for Float { @@ -305,4 +358,61 @@ mod tests { ); } } + + #[test] + fn test_lt_eq_gt() { + let negone = Float::parse("-1".to_string()).unwrap(); + let zero = Float::parse("0".to_string()).unwrap(); + let three = Float::parse("3".to_string()).unwrap(); + + assert!(negone.lt(zero).unwrap()); + assert!(!negone.eq(zero).unwrap()); + assert!(!negone.gt(zero).unwrap()); + + assert!(!three.lt(zero).unwrap()); + assert!(!three.eq(zero).unwrap()); + assert!(three.gt(zero).unwrap()); + + assert!(zero.lt(three).unwrap()); + assert!(!zero.eq(three).unwrap()); + assert!(!zero.gt(three).unwrap()); + } + + proptest! { + #[test] + fn test_lt_eq_gt_with_add(a in reasonable_float()) { + let b = a; + let eq = a.eq(b).unwrap(); + prop_assert!(eq); + + let one = Float::parse("1".to_string()).unwrap(); + + let a = (a - one).unwrap(); + let lt = a.lt(b).unwrap(); + prop_assert!(lt); + + let a = (a + one).unwrap(); + let eq = a.eq(b).unwrap(); + prop_assert!(eq); + + let a = (a + one).unwrap(); + let gt = a.gt(b).unwrap(); + prop_assert!(gt); + } + + #[test] + fn test_exactly_one_lt_eq_gt(a in arb_float(), b in arb_float()) { + let eq = a.eq(b).unwrap(); + let lt = a.lt(b).unwrap(); + let gt = a.gt(b).unwrap(); + + let a_str = a.show_unpacked().unwrap(); + let b_str = b.show_unpacked().unwrap(); + + prop_assert!(lt || eq || gt, "a: {a_str}, b: {b_str}"); + prop_assert!(!(lt && eq), "both less than and equal: a: {a_str}, b: {b_str}"); + prop_assert!(!(eq && gt), "both equal and greater than: a: {a_str}, b: {b_str}"); + prop_assert!(!(lt && gt), "both less than and greater than: a: {a_str}, b: {b_str}"); + } + } } diff --git a/src/concrete/DecimalFloat.sol b/src/concrete/DecimalFloat.sol index 9b59a6f8..43f47dac 100644 --- a/src/concrete/DecimalFloat.sol +++ b/src/concrete/DecimalFloat.sol @@ -171,4 +171,13 @@ contract DecimalFloat { function packLossless(int224 coefficient, int32 exponent) external pure returns (Float) { return LibDecimalFloat.packLossless(coefficient, exponent); } + + /// Exposes `LibDecimalFloat.unpack` for offchain use. + /// @param float The float to unpack. + /// @return coefficient The coefficient of the float. + /// @return exponent The exponent of the float. + function unpack(Float float) external pure returns (int224, int32) { + (int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(float); + return (int224(coefficient), int32(exponent)); + } }