|
|
@@ -0,0 +1,228 @@
|
|
|
+/* This file is part of DarkFi (https://dark.fi)
|
|
|
+ *
|
|
|
+ * Copyright (C) 2020-2023 Dyne.org foundation
|
|
|
+ *
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU Affero General Public License as
|
|
|
+ * published by the Free Software Foundation, either version 3 of the
|
|
|
+ * License, or (at your option) any later version.
|
|
|
+ *
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
+ * GNU Affero General Public License for more details.
|
|
|
+ *
|
|
|
+ * You should have received a copy of the GNU Affero General Public License
|
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
+ */
|
|
|
+
|
|
|
+use std::ops::{Add, AddAssign, Div, Mul, Sub};
|
|
|
+
|
|
|
+use darkfi_sdk::pasta::{group::ff::PrimeField, pallas};
|
|
|
+use dashu::{
|
|
|
+ base::Abs,
|
|
|
+ float::{round::mode::Zero, FBig, Repr},
|
|
|
+ integer::{IBig, Sign, UBig},
|
|
|
+};
|
|
|
+use lazy_static::lazy_static;
|
|
|
+
|
|
|
+const RADIX_BITS: usize = 76;
|
|
|
+const B: u64 = 10;
|
|
|
+
|
|
|
+/// Wrapper structure over a Base 10 [`dashu::float::FBig`]
|
|
|
+/// and Zero rounding mode.
|
|
|
+#[derive(Clone, PartialEq, PartialOrd, Debug)]
|
|
|
+pub struct Float10(FBig<Zero, B>);
|
|
|
+
|
|
|
+impl Float10 {
|
|
|
+ pub fn repr(&self) -> &Repr<B> {
|
|
|
+ self.0.repr()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn abs(&self) -> Self {
|
|
|
+ Self(self.0.clone().abs())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn powf(&self, exp: Self) -> Self {
|
|
|
+ Self(self.0.powf(&exp.0))
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn ln(&self) -> Self {
|
|
|
+ Self(self.0.ln())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Add for Float10 {
|
|
|
+ type Output = Self;
|
|
|
+
|
|
|
+ fn add(self, other: Self) -> Self {
|
|
|
+ Self(self.0 + other.0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AddAssign for Float10 {
|
|
|
+ fn add_assign(&mut self, other: Self) {
|
|
|
+ *self = Self(self.0.clone() + other.0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Sub for Float10 {
|
|
|
+ type Output = Self;
|
|
|
+
|
|
|
+ fn sub(self, other: Self) -> Self {
|
|
|
+ Self(self.0 - other.0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Mul for Float10 {
|
|
|
+ type Output = Self;
|
|
|
+
|
|
|
+ fn mul(self, other: Self) -> Self {
|
|
|
+ Self(self.0 * other.0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Div for Float10 {
|
|
|
+ type Output = Self;
|
|
|
+
|
|
|
+ fn div(self, other: Self) -> Self {
|
|
|
+ Self(self.0 / other.0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::fmt::Display for Float10 {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
+ write!(f, "{}", self.0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<&str> for Float10 {
|
|
|
+ type Error = crate::Error;
|
|
|
+
|
|
|
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
|
+ Ok(Self(FBig::from_str_native(value)?.with_precision(RADIX_BITS).value()))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<u64> for Float10 {
|
|
|
+ type Error = crate::Error;
|
|
|
+
|
|
|
+ fn try_from(value: u64) -> Result<Self, Self::Error> {
|
|
|
+ Ok(Self(FBig::try_from(value)?))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<i64> for Float10 {
|
|
|
+ type Error = crate::Error;
|
|
|
+
|
|
|
+ fn try_from(value: i64) -> Result<Self, Self::Error> {
|
|
|
+ Ok(Self(FBig::try_from(value)?))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl TryFrom<f64> for Float10 {
|
|
|
+ type Error = crate::Error;
|
|
|
+
|
|
|
+ fn try_from(value: f64) -> Result<Self, Self::Error> {
|
|
|
+ Ok(Self(FBig::try_from(value)?.with_base().value()))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Commonly used Float10
|
|
|
+lazy_static! {
|
|
|
+ pub static ref FLOAT10_NEG_TWO: Float10 = Float10::try_from("-2").unwrap();
|
|
|
+ pub static ref FLOAT10_NEG_ONE: Float10 = Float10::try_from("-1").unwrap();
|
|
|
+ pub static ref FLOAT10_ZERO: Float10 = Float10::try_from("0").unwrap();
|
|
|
+ pub static ref FLOAT10_ONE: Float10 = Float10::try_from("1").unwrap();
|
|
|
+ pub static ref FLOAT10_TWO: Float10 = Float10::try_from("2").unwrap();
|
|
|
+ pub static ref FLOAT10_THREE: Float10 = Float10::try_from("3").unwrap();
|
|
|
+ pub static ref FLOAT10_FIVE: Float10 = Float10::try_from("5").unwrap();
|
|
|
+ pub static ref FLOAT10_NINE: Float10 = Float10::try_from("9").unwrap();
|
|
|
+ pub static ref FLOAT10_TEN: Float10 = Float10::try_from("10").unwrap();
|
|
|
+}
|
|
|
+
|
|
|
+// Utility functions
|
|
|
+/// Convert a Float10 to [`dashu::integer::IBig`].
|
|
|
+pub fn fbig2ibig(f: Float10) -> IBig {
|
|
|
+ let rad = IBig::try_from(10).unwrap();
|
|
|
+ let sig = f.repr().significand();
|
|
|
+ let exp = f.repr().exponent();
|
|
|
+
|
|
|
+ let val: IBig = if exp >= 0 {
|
|
|
+ sig.clone() * rad.pow(exp.unsigned_abs())
|
|
|
+ } else {
|
|
|
+ sig.clone() / rad.pow(exp.unsigned_abs())
|
|
|
+ };
|
|
|
+
|
|
|
+ val
|
|
|
+}
|
|
|
+
|
|
|
+/// Convert a Float10 to [`pallas::Base`].
|
|
|
+/// Note: negative values in pallas field don't wrap,
|
|
|
+/// and can't be converted back to original value.
|
|
|
+pub fn fbig2base(f: Float10) -> pallas::Base {
|
|
|
+ let val: IBig = fbig2ibig(f);
|
|
|
+ let (sign, word) = val.as_sign_words();
|
|
|
+ let mut words: [u64; 4] = [0, 0, 0, 0];
|
|
|
+ words[..word.len()].copy_from_slice(word);
|
|
|
+ match sign {
|
|
|
+ Sign::Positive => pallas::Base::from_raw(words),
|
|
|
+ Sign::Negative => pallas::Base::from_raw(words).neg(),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Convert a [`pallas::Base`] to [`dashu::integer::IBig`].
|
|
|
+/// Note: only zero and positive numbers conversion is supported.
|
|
|
+/// Used for testing purposes on non-negative values at the moment.
|
|
|
+pub fn base2ibig(base: pallas::Base) -> IBig {
|
|
|
+ let byts: [u8; 32] = base.to_repr();
|
|
|
+ let words: [u64; 4] = [
|
|
|
+ u64::from_le_bytes(byts[0..8].try_into().expect("")),
|
|
|
+ u64::from_le_bytes(byts[8..16].try_into().expect("")),
|
|
|
+ u64::from_le_bytes(byts[16..24].try_into().expect("")),
|
|
|
+ u64::from_le_bytes(byts[24..32].try_into().expect("")),
|
|
|
+ ];
|
|
|
+ let uparts = UBig::from_words(&words);
|
|
|
+ IBig::from_parts(Sign::Positive, uparts)
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use darkfi_sdk::pasta::pallas;
|
|
|
+ use dashu::integer::IBig;
|
|
|
+
|
|
|
+ use super::{base2ibig, fbig2base, fbig2ibig, Float10};
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn dashu_fbig2ibig() {
|
|
|
+ let f = Float10::try_from("234234223.000").unwrap();
|
|
|
+ let i: IBig = fbig2ibig(f);
|
|
|
+ let sig = IBig::from(234234223);
|
|
|
+ assert_eq!(i, sig);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn dashu_test_base2ibig() {
|
|
|
+ let fbig: Float10 = Float10::try_from(
|
|
|
+ "289480223093290488558927462521719769633630564819415607159546767643499676303",
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ let ibig = fbig2ibig(fbig.clone());
|
|
|
+ let res_base: pallas::Base = fbig2base(fbig.clone());
|
|
|
+ let res_ibig: IBig = base2ibig(res_base);
|
|
|
+ assert_eq!(res_ibig, ibig);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn dashu_test2_base2ibig() {
|
|
|
+ // Verify that field wrapping for negative values won't hold during conversions.
|
|
|
+ let fbig: Float10 = Float10::try_from(
|
|
|
+ "-20065240046497827215558476051577517633529246907153511707181011345840062564.87",
|
|
|
+ )
|
|
|
+ .unwrap();
|
|
|
+ let ibig = fbig2ibig(fbig.clone());
|
|
|
+ let res_base: pallas::Base = fbig2base(fbig.clone());
|
|
|
+ let res_ibig: IBig = base2ibig(res_base);
|
|
|
+ assert_ne!(res_ibig, ibig);
|
|
|
+ }
|
|
|
+}
|