val 0.3.6

An arbitrary precision calculator language
Documentation
use super::*;

pub trait FloatExt {
  fn display(&self) -> String;
  fn to_f64(&self, rounding_mode: astro_float::RoundingMode) -> Option<f64>;
}

impl FloatExt for Float {
  fn display(&self) -> String {
    match () {
      _ if self.is_nan() => "nan".into(),
      _ if self.is_inf_pos() => "inf".into(),
      _ if self.is_inf_neg() => "-inf".into(),
      _ if self.is_zero() => "0".into(),
      _ => {
        let formatted = self
          .format(
            Radix::Dec,
            astro_float::RoundingMode::None,
            &mut Consts::new().unwrap(),
          )
          .unwrap();

        if !formatted.contains('e') {
          return formatted;
        }

        let (mantissa, exponent) = {
          let mut parts = formatted.split('e');
          (parts.next().unwrap(), parts.next().unwrap())
        };

        let exponent = exponent.parse::<i32>().unwrap();

        let (sign, mantissa) =
          mantissa.split_at(if mantissa.starts_with('-') { 1 } else { 0 });

        let digits = mantissa.replace('.', "");

        let length =
          mantissa.find('.').unwrap_or(mantissa.len()) as i32 + exponent;

        let result = match length {
          length if length <= 0 => {
            format!("{}0.{}{}", sign, "0".repeat((-length) as usize), digits)
          }
          length if (length as usize) >= digits.len() => {
            format!(
              "{}{}{}",
              sign,
              digits,
              "0".repeat(length as usize - digits.len())
            )
          }
          _ => {
            let (left, right) = digits.split_at(length as usize);
            format!("{}{}.{}", sign, left, right)
          }
        };

        if result.find('.').is_some() {
          return result
            .trim_end_matches('0')
            .trim_end_matches('.')
            .to_string();
        }

        result
      }
    }
  }

  fn to_f64(&self, rounding_mode: astro_float::RoundingMode) -> Option<f64> {
    let mut big_float = self.clone();

    big_float.set_precision(64, rounding_mode).ok()?;

    let sign = big_float.sign()?;

    let exponent = big_float.exponent()? as isize;

    let mantissa_digits = big_float.mantissa_digits()?;

    if mantissa_digits.is_empty() {
      return Some(0.0);
    }

    let mantissa = mantissa_digits[0];

    if mantissa == 0 {
      return Some(0.0);
    }

    let mut exponent = exponent + 0b1111111111;

    let mut ret = 0u64;

    if exponent >= 0b11111111111 {
      Some(match sign {
        Sign::Pos => f64::INFINITY,
        Sign::Neg => f64::NEG_INFINITY,
      })
    } else if exponent <= 0 {
      let shift = -exponent;

      if shift < 52 {
        ret |= mantissa >> (shift + 12);

        if sign == Sign::Neg {
          ret |= 0x8000000000000000u64;
        }

        return Some(f64::from_bits(ret));
      } else {
        return Some(0.0);
      }
    } else {
      let mantissa = mantissa << 1;

      exponent -= 1;

      if sign == Sign::Neg {
        ret |= 1;
      }

      ret <<= 11;
      ret |= exponent as u64;
      ret <<= 52;
      ret |= mantissa >> 12;

      return Some(f64::from_bits(ret));
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  fn float_from_str(s: &str) -> Float {
    Float::parse(
      s,
      Radix::Dec,
      128,
      astro_float::RoundingMode::FromZero,
      &mut Consts::new().unwrap(),
    )
  }

  #[test]
  fn specials() {
    assert_eq!(format!("{}", Float::from(0).display()), "0");

    assert_eq!(format!("{}", Float::from(f64::INFINITY).display()), "inf");

    assert_eq!(
      format!("{}", Float::from(f64::NEG_INFINITY).display()),
      "-inf"
    );

    assert_eq!(format!("{}", Float::nan(None).display()), "nan");
  }

  #[test]
  fn integers() {
    assert_eq!(Float::from(1).display(), "1");
    assert_eq!(Float::from(-1).display(), "-1");
    assert_eq!(Float::from(123456789).display(), "123456789");
    assert_eq!(Float::from(-123456789).display(), "-123456789");
  }

  #[test]
  fn trailing_zeros() {
    assert_eq!(float_from_str("1.2300e2").display(), "123");
  }

  #[test]
  fn scientific_notation_positive_exponent() {
    assert_eq!(float_from_str("1.23e2").display(), "123");
    assert_eq!(float_from_str("1.23e3").display(), "1230");
  }

  #[test]
  fn scientific_notation_negative_exponent() {
    assert_eq!(float_from_str("1.23e-5").display(), "0.0000123");
    assert_eq!(float_from_str("-1.23e-2").display(), "-0.0123");
    assert_eq!(float_from_str("-1.23e-5").display(), "-0.0000123");
  }

  #[test]
  fn large_numbers() {
    assert_eq!(float_from_str("1e15").display(), "1000000000000000");
    assert_eq!(float_from_str("-1e15").display(), "-1000000000000000");
    assert_eq!(float_from_str("1.23e15").display(), "1230000000000000");
  }

  #[test]
  fn convert_to_double_precision() {
    assert_eq!(
      Float::from(0.0).to_f64(astro_float::RoundingMode::ToEven),
      Some(0.0)
    );

    assert_eq!(
      Float::from(1.0).to_f64(astro_float::RoundingMode::ToEven),
      Some(1.0)
    );

    assert_eq!(
      Float::from(-1.0).to_f64(astro_float::RoundingMode::ToEven),
      Some(-1.0)
    );
  }
}