use core::cmp::Ordering::{self, Equal, Greater, Less};
use malachite_base::num::arithmetic::traits::{Abs, Pow};
use malachite_base::num::basic::traits::{One, Two, Zero};
use malachite_base::num::conversion::traits::{ExactFrom, IntegerMantissaAndExponent};
use malachite_base::num::logic::traits::{BitAccess, SignificantBits};
use malachite_base::rounding_modes::RoundingMode::{self, *};
use malachite_float::Float;
use malachite_float::float::conversion::string::get_str::get_str;
use malachite_float::test_util::common::{parse_hex_string, rug_round_try_from_rounding_mode};
use malachite_float::test_util::generators::{
float_signed_unsigned_rounding_mode_quadruple_gen_var_9,
float_signed_unsigned_rounding_mode_quadruple_gen_var_10,
float_signed_unsigned_rounding_mode_quadruple_gen_var_15,
float_signed_unsigned_rounding_mode_quadruple_gen_var_16,
};
use malachite_nz::natural::Natural;
use malachite_q::Rational;
use std::panic::catch_unwind;
fn digit_value(c: u8, large_table: bool) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
b'A'..=b'Z' => c - b'A' + 10,
b'a'..=b'z' if large_table => c - b'a' + 36,
b'a'..=b'z' => c - b'a' + 10,
_ => panic!("invalid digit character {c}"),
}
}
const POWER_PREC: u64 = 256;
const POWER_PREC_RETRY: u64 = 2048;
fn power_bracket(b: u64, e: u64, prec: u64) -> (Natural, Natural, i64) {
let (mut lo, mut err, mut shift) = (Natural::ONE, Natural::ZERO, 0i64);
for i in (0..e.significant_bits()).rev() {
err = ((&lo * &err) << 1u64) + (&err * &err);
lo = &lo * &lo;
shift *= 2;
if e.get_bit(i) {
lo *= Natural::from(b);
err *= Natural::from(b);
}
let bits = lo.significant_bits();
if bits > prec {
let j = bits - prec;
lo >>= j;
err = (err >> j) + Natural::TWO;
shift += i64::exact_from(j);
}
}
(lo, err, shift)
}
fn cmp_scaled(a: &Natural, u: i64, c: &Natural, w: i64) -> Ordering {
if *a == 0u32 || *c == 0u32 {
return a.cmp(c);
}
let la = i64::exact_from(a.significant_bits()) + u;
let lc = i64::exact_from(c.significant_bits()) + w;
if la != lc {
return la.cmp(&lc);
}
let d = u - w;
if d >= 0 {
(a << u64::exact_from(d)).cmp(c)
} else {
a.cmp(&(c << u64::exact_from(-d)))
}
}
fn cmp_mag_vs(mag: &Natural, mag_exp: i64, n: &Natural, b: u64, t: i64) -> Option<Ordering> {
for prec in [POWER_PREC, POWER_PREC_RETRY] {
let (lo, err, k) = power_bracket(b, t.unsigned_abs(), prec);
let hi = &lo + &err;
let decided = if t >= 0 {
if cmp_scaled(mag, mag_exp, &(n * &lo), k) == Less {
Some(Less)
} else if cmp_scaled(mag, mag_exp, &(n * &hi), k) != Less {
Some(Greater)
} else {
None
}
} else {
if cmp_scaled(&(mag * &hi), mag_exp + k, n, 0) != Greater {
Some(Less)
} else if cmp_scaled(&(mag * &lo), mag_exp + k, n, 0) == Greater {
Some(Greater)
} else {
None
}
};
if decided.is_some() {
return decided;
}
}
None
}
fn verify_get_str_bracketed(
x: &Float,
neg: bool,
d: &Natural,
b: u64,
exp: i64,
m_actual: usize,
rnd: RoundingMode,
ord: Ordering,
) {
let (mag, mag_exp) = x.integer_mantissa_and_exponent();
let t = exp - i64::exact_from(m_actual);
let mag_rnd = match (rnd, neg) {
(Down, _) | (Floor, false) | (Ceiling, true) => Floor,
(Up, _) | (Ceiling, false) | (Floor, true) => Ceiling,
(rnd, _) => rnd,
};
let cmp = |n: &Natural| cmp_mag_vs(&mag, mag_exp, n, b, t);
let one = Natural::ONE;
let ctx = format!("{x} base {b} m {m_actual} {rnd}");
let b_nat = Natural::from(b);
let m_bits = u64::exact_from(m_actual);
let smallest = (&b_nat).pow(m_bits - 1);
let largest = (&b_nat).pow(m_bits) - &one;
let narrow = *d == smallest;
let (pred_n, pred_t) = if narrow {
(largest.clone(), t - 1)
} else {
(d - &one, t)
};
match mag_rnd {
Floor => {
assert_ne!(cmp(d), Some(Less), "{ctx}: |x| below the digits");
assert_eq!(
cmp(&(d + &one)),
Some(Less),
"{ctx}: |x| at or above the next digit string"
);
}
Ceiling => {
assert_ne!(cmp(d), Some(Greater), "{ctx}: |x| above the digits");
assert_eq!(
cmp_mag_vs(&mag, mag_exp, &pred_n, b, pred_t),
Some(Greater),
"{ctx}: |x| at or below the previous digit string"
);
}
Nearest => {
let two_d = d << 1u64;
let (lo_mid_n, lo_mid_t) = if narrow {
((&two_d * &b_nat) - &one, t - 1)
} else {
(&two_d - &one, t)
};
assert_ne!(
cmp_mag_vs(&mag, mag_exp + 1, &lo_mid_n, b, lo_mid_t),
Some(Less),
"{ctx}: |x| more than halfway toward the previous digit string"
);
assert_ne!(
cmp_mag_vs(&mag, mag_exp + 1, &(&two_d + &one), b, t),
Some(Greater),
"{ctx}: |x| more than half an ulp above the digits"
);
}
Exact => {}
_ => unreachable!(),
}
if let Some(c) = cmp(d) {
assert_eq!(ord, if neg { c } else { c.reverse() }, "{ctx}: ordering");
}
}
fn verify_get_str_exactly(
x: &Float,
neg: bool,
d: &Natural,
b: u64,
exp: i64,
m_actual: usize,
rnd: RoundingMode,
ord: Ordering,
) {
let ulp = Rational::from(b).pow(exp - i64::exact_from(m_actual));
let narrow = *d == Natural::from(b).pow(u64::exact_from(m_actual) - 1);
let toward_zero = if narrow {
&ulp / Rational::from(b)
} else {
ulp.clone()
};
let (below, above) = if neg {
(ulp.clone(), toward_zero)
} else {
(toward_zero, ulp.clone())
};
let mut v = Rational::from(d.clone()) * &ulp;
if neg {
v = -v;
}
let x_rat = Rational::exact_from(x);
let eff = match rnd {
Down => {
if neg {
Ceiling
} else {
Floor
}
}
Up => {
if neg {
Floor
} else {
Ceiling
}
}
rnd => rnd,
};
match eff {
Floor => {
assert!(v <= x_rat);
assert!(&x_rat - &v < above);
}
Ceiling => {
assert!(v >= x_rat);
assert!(&v - &x_rat < below);
}
Nearest => {
let gap = if v >= x_rat { &below } else { &above };
assert!((&v - &x_rat).abs() * Rational::TWO <= *gap);
}
Exact => assert_eq!(v, x_rat),
_ => unreachable!(),
}
assert_eq!(ord, v.cmp(&x_rat));
match (x_rat >= 0u32, rnd) {
(_, Floor) | (true, Down) | (false, Up) => assert_ne!(ord, Greater),
(_, Ceiling) | (true, Up) | (false, Down) => assert_ne!(ord, Less),
(_, Exact) => assert_eq!(ord, Equal),
_ => {}
}
}
const MAX_RATIONAL_EXPONENT: u64 = 10_000;
fn verify_get_str(x: &Float, b0: i64, m: usize, rnd: RoundingMode) {
if !((-36..=-2).contains(&b0) || (2..=62).contains(&b0)) {
assert!(get_str(x, b0, m, rnd).is_none());
return;
}
let (digits, exp, ord) = get_str(x, b0, m, rnd).unwrap();
let b = b0.unsigned_abs();
if x.is_nan() {
assert_eq!(digits, b"@NaN@");
assert_eq!(ord, Equal);
return;
}
if x.is_infinite() {
let expected: &[u8] = if x.is_sign_negative() {
b"-@Inf@"
} else {
b"@Inf@"
};
assert_eq!(digits, expected);
assert_eq!(ord, Equal);
return;
}
if x.is_zero() {
let mut expected = vec![b'0'; if m == 0 { 1 } else { m }];
if x.is_sign_negative() {
expected.insert(0, b'-');
}
assert_eq!(digits, expected);
assert_eq!(ord, Equal);
return;
}
let neg = digits[0] == b'-';
let digit_bytes = &digits[usize::from(neg)..];
let m_actual = digit_bytes.len();
if m != 0 {
assert_eq!(m_actual, m);
}
let large_table = !(2..=36).contains(&b0);
let mut d = Natural::ZERO;
for &c in digit_bytes {
d = d * Natural::from(b) + Natural::from(digit_value(c, large_table));
}
assert!(d >= Natural::from(b).pow(u64::exact_from(m_actual - 1)));
assert!(d < Natural::from(b).pow(u64::exact_from(m_actual)));
if exp.unsigned_abs() > MAX_RATIONAL_EXPONENT {
verify_get_str_bracketed(x, neg, &d, b, exp, m_actual, rnd, ord);
} else {
verify_get_str_exactly(x, neg, &d, b, exp, m_actual, rnd, ord);
}
if b0 < 0 {
let (pos_digits, pos_exp, pos_ord) = get_str(x, -b0, m, rnd).unwrap();
assert_eq!(digits, pos_digits.to_ascii_uppercase());
assert_eq!(exp, pos_exp);
assert_eq!(ord, pos_ord);
}
if ord == Equal {
for other in [Floor, Ceiling, Down, Up, Nearest, Exact] {
assert_eq!(
get_str(x, b0, m, other).unwrap(),
(digits.clone(), exp, Equal),
"{x} base {b0} m {m} disagrees under {other}"
);
}
}
if (2..=36).contains(&b0)
&& let Ok(round) = rug_round_try_from_rounding_mode(rnd)
{
let (rug_neg, rug_mant, rug_exp) = rug::Float::exact_from(x).to_sign_string_exp_round(
i32::exact_from(b),
Some(m_actual),
round,
);
assert_eq!(neg, rug_neg);
assert_eq!(digit_bytes, rug_mant.as_bytes());
assert_eq!(exp, i64::from(rug_exp.unwrap()));
}
}
#[test]
fn test_get_str() {
fn test(
s: &str,
s_hex: &str,
b0: i64,
m: usize,
rnd: RoundingMode,
out: &str,
exp: i64,
ord: Ordering,
) {
let x = parse_hex_string(s_hex);
assert_eq!(x.to_string(), s);
let (digits, e, o) = get_str(&x, b0, m, rnd).unwrap();
assert_eq!(std::str::from_utf8(&digits).unwrap(), out);
assert_eq!(e, exp);
assert_eq!(o, ord);
verify_get_str(&x, b0, m, rnd);
}
test("1.2", "0x1.4#3", 10, 3, Nearest, "125", 1, Equal);
test("1.2", "0x1.4#3", 2, 3, Nearest, "101", 1, Equal);
test(
"0.333333343",
"0x0.5555558#25",
10,
4,
Floor,
"3333",
0,
Less,
);
test(
"0.333333343",
"0x0.5555558#25",
10,
4,
Ceiling,
"3334",
0,
Greater,
);
test("NaN", "NaN", 2, 0, Down, "@NaN@", 0, Equal);
test("Infinity", "Infinity", 2, 0, Down, "@Inf@", 0, Equal);
test("-Infinity", "-Infinity", 2, 0, Down, "-@Inf@", 0, Equal);
test("0.0", "0x0.0", 2, 0, Down, "0", 0, Equal);
test("0.0", "0x0.0", 2, 1, Down, "0", 0, Equal);
test("-0.0", "-0x0.0", 2, 0, Down, "-0", 0, Equal);
test("1.0", "0x1.0#1", 2, 0, Down, "1", 1, Equal);
test("1.0", "0x1.0#1", 2, 1, Down, "1", 1, Equal);
test("1.0", "0x1.0#1", 3, 0, Down, "10", 1, Equal);
test("-1.0", "-0x1.0#1", 2, 0, Down, "-1", 1, Equal);
test("-1.0", "-0x1.0#1", 2, 0, Floor, "-1", 1, Equal);
test("-1.0", "-0x1.0#1", 2, 0, Ceiling, "-1", 1, Equal);
test("2.0", "0x2.0#1", 3, 1, Down, "2", 1, Equal);
test("1.0", "0x1.0#1", 9, 15, Down, "100000000000000", 1, Equal);
test("0.50", "0x0.8#1", 3, 1, Up, "2", 0, Greater);
test("0.50", "0x0.8#1", 4, 0, Down, "2", 0, Equal);
test("4.0", "0x4.0#1", 3, 1, Down, "1", 2, Less);
test(
"1.0",
"0x1.0#1",
9,
20,
Down,
"10000000000000000000",
1,
Equal,
);
test(
"0.50",
"0x0.8#1",
16,
16,
Down,
"8000000000000000",
0,
Equal,
);
test("1.5", "0x1.8#2", 2, 1, Up, "1", 2, Greater);
test("1.5", "0x1.8#2", 3, 0, Down, "111", 1, Less);
test("1.5", "0x1.8#2", 3, 0, Up, "112", 1, Greater);
test("1.5", "0x1.8#2", 3, 0, Nearest, "111", 1, Less);
test("1.5", "0x1.8#2", 3, 2, Nearest, "12", 1, Greater);
test("1.5", "0x1.8#2", 4, 1, Up, "2", 1, Greater);
test("1.5", "0x1.8#2", 6, 1, Nearest, "2", 1, Greater);
test("0.75", "0x0.c#2", 3, 1, Up, "1", 1, Greater);
test("6.0", "0x6.0#2", 3, 1, Down, "2", 2, Equal);
test(
"-269104312292334.3027",
"-0xf4bfbaf113ee.4d8#57",
43,
2,
Down,
"-N1",
9,
Greater,
);
test(
"0.000199046277632504184666664672269768242929310652018203552191617720205649",
"0x0.000d0b7140b8f3aea60aad60c1dc3b2ee0d83e2eba33dcfb6f874df52d78#225",
7,
6,
Down,
"322631",
-4,
Less,
);
test(
"1.1595752615776271305e-33",
"0x6.055703bef650178E-28#63",
28,
2,
Down,
"26",
-22,
Less,
);
test(
"1.04226364758062811487679885e63",
"0x2.889a2dba3978ccd56c826E+52#85",
26,
11,
Up,
"5j89gbd3609",
45,
Greater,
);
test(
"13863336.632654341786855779405528442674244",
"0xd389a8.a1f5a28ba59ea1aca395f84bcc2#131",
29,
2,
Nearest,
"jh",
5,
Less,
);
test(
"1858.9712372",
"0x742.f8a300#32",
34,
6,
Nearest,
"1kmx0q",
3,
Greater,
);
test(
"4.518487544134823141772917e44",
"0x1.442f82545e664fc1b2dcE+37#79",
23,
7,
Ceiling,
"c07e6ek",
33,
Greater,
);
test(
"4.205885e17",
"0x5.d63bE+14#19",
39,
3,
Nearest,
"1CQ",
12,
Less,
);
test(
"3387429.4861150337642728810837723",
"0x33b025.7c7208ec1c8a76dc0e59#102",
25,
5,
Floor,
"8gjm4",
5,
Less,
);
test(
"-1.4772e19",
"-0xc.d0E+15#11",
40,
12,
Exact,
"-Z8TcXJ48HaW0",
12,
Equal,
);
test(
"-4.9275860538295e71",
"-0x4.7656eb83f9E+59#43",
56,
13,
Up,
"-12CZ7Gm3q4fS0",
42,
Less,
);
test(
"1.666502136661e30",
"0x1.508c2421bE+25#37",
18,
12,
Nearest,
"1478827641f3",
25,
Less,
);
test(
"-1.65839",
"-0x1.a88c#16",
38,
14,
Nearest,
"-1P0QaVM1O1TQ4S",
1,
Greater,
);
test(
"7.19867e-15",
"0x2.06b8E-12#16",
46,
8,
Down,
"6TH73LXN",
-8,
Less,
);
let x = parse_hex_string("0x1.0#1");
assert!(get_str(&x, 100, 0, Nearest).is_none());
assert!(get_str(&x, 63, 0, Nearest).is_none());
assert!(get_str(&x, 1, 0, Nearest).is_none());
assert!(get_str(&x, 0, 0, Nearest).is_none());
assert!(get_str(&x, -1, 0, Nearest).is_none());
assert!(get_str(&x, -37, 0, Nearest).is_none());
verify_get_str(&x, 100, 0, Nearest);
verify_get_str(&x, -37, 0, Nearest);
}
#[test]
fn test_get_str_exact_panics() {
assert_panic!(get_str(&parse_hex_string("0x0.5555558#25"), 10, 4, Exact));
assert_panic!(get_str(&parse_hex_string("0x0.8#1"), 3, 2, Exact));
assert_panic!(get_str(
&parse_hex_string("0x0.000d0b7140b8f3aea60aad60c1dc3b2ee0d83e2eba33dcfb6f874df52d78#225"),
7,
6,
Exact
));
assert_panic!(get_str(&parse_hex_string("0x1.921fb6#24"), 10, 0, Exact));
}
#[test]
fn get_str_properties() {
float_signed_unsigned_rounding_mode_quadruple_gen_var_9().test_properties(|(x, b0, m, rnd)| {
verify_get_str(&x, b0, m, rnd);
});
float_signed_unsigned_rounding_mode_quadruple_gen_var_10().test_properties(
|(x, b0, m, rnd)| {
verify_get_str(&x, b0, m, rnd);
},
);
float_signed_unsigned_rounding_mode_quadruple_gen_var_15().test_properties(
|(x, b0, m, rnd)| {
verify_get_str(&x, b0, m, rnd);
},
);
float_signed_unsigned_rounding_mode_quadruple_gen_var_16().test_properties(
|(x, b0, m, rnd)| {
verify_get_str(&x, b0, m, rnd);
},
);
}