diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index deaa7776..e261a8d5 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -12,7 +12,7 @@ use revm::interpreter::interpreter::EthInterpreter; use revm::primitives::{address, fixed_bytes}; use revm::{Context, MainBuilder, MainContext, SystemCallEvm}; use std::cell::RefCell; -use std::ops::{Add, Div, Mul, Sub}; +use std::ops::{Add, Div, Mul, Neg, Sub}; use std::thread::AccessError; use thiserror::Error; @@ -232,6 +232,16 @@ impl Float { }) } + pub fn inv(self) -> Result { + let Float(a) = self; + let calldata = DecimalFloat::invCall { a }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::invCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) + }) + } + pub fn abs(self) -> Result { let Float(a) = self; let calldata = DecimalFloat::absCall { a }.abi_encode(); @@ -303,6 +313,20 @@ impl Div for Float { } } +impl Neg for Float { + type Output = Result; + + fn neg(self) -> Self::Output { + let Float(a) = self; + let calldata = DecimalFloat::minusCall { a }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::minusCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) + }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -531,6 +555,64 @@ mod tests { } } + #[test] + fn test_minus_format() { + let float = Float::parse("-123.1234234625468391".to_string()).unwrap(); + let negated = float.neg().unwrap(); + let formatted = negated.format().unwrap(); + assert_eq!(formatted, "123.1234234625468391"); + + let float = Float::parse(formatted).unwrap(); + let negated = float.neg().unwrap(); + let formatted = negated.format().unwrap(); + assert_eq!(formatted, "-123.1234234625468391"); + + let float = Float::parse("0".to_string()).unwrap(); + let negated = float.neg().unwrap(); + let formatted = negated.format().unwrap(); + assert_eq!(formatted, "0"); + } + + proptest! { + #[test] + fn test_minus_minus(float in arb_float()) { + let negated = float.neg().unwrap(); + let renegated = negated.neg().unwrap(); + prop_assert!(float.eq(renegated).unwrap()); + } + } + + proptest! { + #[test] + fn test_inv_prod(float in reasonable_float()) { + let zero = Float::parse("0".to_string()).unwrap(); + prop_assume!(!float.eq(zero).unwrap()); + + let inv = float.inv().unwrap(); + let product = (float * inv).unwrap(); + let one = Float::parse("1".to_string()).unwrap(); + + // Allow for minor rounding errors introduced by the lossy + // `inv` implementation. We consider the property to + // hold if the product is within `±1e-37` of 1. + + let eps = Float::parse("1e-37".to_string()).unwrap(); + let one_plus_eps = (one + eps).unwrap(); + let one_minus_eps = (one - eps).unwrap(); + + let within_upper = !product.gt(one_plus_eps).unwrap(); + let within_lower = !product.lt(one_minus_eps).unwrap(); + + prop_assert!( + within_upper && within_lower, + "float: {}, inv: {}, product: {} (not within ±ε)", + float.show_unpacked().unwrap(), + inv.show_unpacked().unwrap(), + product.show_unpacked().unwrap(), + ); + } + } + proptest! { #[test] fn test_abs_no_minus_sign(float in reasonable_float()) {