1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::math::compare::nz_compare_mantissa;
use crate::math::sub::p_sub;
use crate::ptr::Ptr;
use crate::types::builder::Builder;
use crate::types::error::Error;
use crate::types::mantissa::MANTISSA_1;
use crate::types::precision::Precision;
use crate::types::rounding::{Rounding, Truncate};
use crate::types::scientific::{s_unsafe_static_new, Scientific};
use core::cmp::Ordering;

#[inline(always)]
fn div_results_in_zero(lhs: &Scientific, rhs: &Scientific, precision: Precision) -> bool {
  match precision {
    Precision::Digits(digits) => digits <= 0,
    Precision::Decimals(decimals) => lhs.exponent0() - rhs.exponent0() + decimals < 0,
  }
}

pub(crate) fn export_div<R: Rounding>(
  lhs: &Scientific,
  rhs: &Scientific,
  precision: Precision,
  rounding: R,
) -> Result<Scientific, Error> {
  if rhs.is_zero() {
    Err(Error::DivisionByZero)
  } else if lhs.is_zero() || div_results_in_zero(lhs, rhs, precision) {
    Ok(Scientific::ZERO)
  } else if rhs.len == 1 && *rhs.data == 1 {
    let mut r = (lhs >> rhs.exponent).round_r(precision, rounding);
    if rhs.sign.is_negative() {
      r.neg_assign();
    }
    Ok(r)
  } else if lhs.len == rhs.len && nz_compare_mantissa::<false>(lhs, rhs) == Ordering::Equal {
    let exponent = lhs.exponent0() - rhs.exponent0();
    if let Precision::Decimals(decimals) = precision {
      if -decimals > exponent {
        return Ok(Scientific::ZERO);
      }
    }
    Ok(s_unsafe_static_new(
      lhs.sign ^ rhs.sign,
      &MANTISSA_1,
      exponent,
    ))
  } else {
    let extra_digits = match precision {
      Precision::Digits(digits) => digits - (lhs.len - rhs.len),
      Precision::Decimals(decimals) => lhs.exponent - rhs.exponent + decimals,
    };
    Ok(nz_div(lhs, rhs, extra_digits, precision, rounding))
  }
}

#[inline(always)]
fn nz_div<R: Rounding>(
  lhs: &Scientific,
  rhs: &Scientific,
  extra_digits: isize,
  precision: Precision,
  rounding: R,
) -> Scientific {
  // Notice: extra_digits can be negative!
  // n1.len + decimals is guaranteed to be >= n2.len
  #[cfg(feature = "debug")]
  assert!(lhs.len + extra_digits >= rhs.len);

  let round_digits = isize::from(!<R>::is_truncate());
  let extra_digits = extra_digits + round_digits;

  let mut tmp = vec![0; (lhs.len + extra_digits) as usize];
  let mut tmp_ptr = Ptr::new_mut(tmp.as_mut_ptr(), lhs.len + extra_digits);
  lhs
    .data
    .copy_to_nonoverlapping(lhs.len + extra_digits.min(0), tmp_ptr, 0);
  let mut tmp_len = 0;
  let (result, mut result_ptr) = Builder::new(
    lhs.sign ^ rhs.sign,
    lhs.len + extra_digits + round_digits,
    lhs.exponent - rhs.exponent - extra_digits,
  );
  if !<R>::is_truncate() {
    result_ptr.inc();
  }
  let result_end = result_ptr.offset(lhs.len + extra_digits);
  while result_ptr < result_end {
    tmp_len += 1;
    p_trim(&mut tmp_ptr, &mut tmp_len);
    let mut j = 0;
    while p_ge(tmp_ptr, tmp_len, rhs) {
      p_sub(tmp_ptr, tmp_len, rhs);
      p_trim(&mut tmp_ptr, &mut tmp_len);
      j += 1;
    }
    *result_ptr = j;
    result_ptr.inc();
  }

  if <R>::is_truncate() {
    result.round(precision, Truncate)
  } else {
    result.round(precision, rounding)
  }
}

// Remove leading zeroes, this is important because p_ge assumes that there are no leading zeroes
#[inline(always)]
fn p_trim(value_ptr: &mut Ptr, value_len: &mut isize) {
  while *value_len > 0 && **value_ptr == 0 {
    value_ptr.inc();
    *value_len -= 1;
  }
}

// Compare two mantissa (the exponent and sign is ignored)
#[inline(always)]
fn p_ge(mut lhs_ptr: Ptr, lhs_len: isize, rhs: &Scientific) -> bool {
  if lhs_len != rhs.len {
    lhs_len >= rhs.len
  } else {
    let mut rhs_ptr = rhs.data;
    let rhs_end = rhs.data.offset(rhs.len);
    while rhs_ptr < rhs_end {
      if *lhs_ptr != *rhs_ptr {
        return *lhs_ptr >= *rhs_ptr;
      }
      lhs_ptr.inc();
      rhs_ptr.inc();
    }
    true
  }
}