// Copyright © 2026 Mikhail Hogrefe
//
// This file is part of Malachite.
//
// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.

use core::cmp::Ordering::{self, *};
use malachite_base::num::arithmetic::traits::{IsPowerOf2, LogBase, LogBaseAssign, Reciprocal};
use malachite_base::num::basic::floats::PrimitiveFloat;
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::basic::traits::{Infinity, NaN, NegativeInfinity, NegativeZero, Zero};
use malachite_base::num::conversion::traits::{ExactFrom, RoundingFrom};
use malachite_base::num::float::NiceFloat;
use malachite_base::num::logic::traits::SignificantBits;
use malachite_base::rounding_modes::RoundingMode::{self, *};
use malachite_base::test_util::generators::common::GenConfig;
use malachite_base::test_util::generators::primitive_float_unsigned_pair_gen_var_4;
use malachite_float::arithmetic::log_base::{
    primitive_float_log_base, primitive_float_log_base_rational,
};
use malachite_float::arithmetic::log_base_power_of_2::{
    primitive_float_log_base_power_of_2, primitive_float_log_base_power_of_2_rational,
};
use malachite_float::test_util::arithmetic::log_base::{
    rug_log_base, rug_log_base_prec_round, rug_log_base_rational_prec_round,
};
use malachite_float::test_util::common::rug_round_try_from_rounding_mode;
use malachite_float::test_util::generators::{
    float_unsigned_rounding_mode_triple_gen_var_27, float_unsigned_rounding_mode_triple_gen_var_28,
    float_unsigned_unsigned_rounding_mode_quadruple_gen_var_5,
    float_unsigned_unsigned_rounding_mode_quadruple_gen_var_6,
    rational_unsigned_unsigned_rounding_mode_quadruple_gen_var_1,
};
use malachite_float::{ComparableFloatRef, Float};
use malachite_nz::platform::Limb;
use malachite_q::Rational;
use malachite_q::test_util::generators::rational_unsigned_pair_gen_var_9;
use std::panic::catch_unwind;
use std::str::FromStr;

// Cross-checks the by-value, by-reference, and assigning variants against each other and against
// the rug oracle, and checks delegation when `base` is a power of 2. Returns the computed `(Float,
// Ordering)`.
fn check(x: &Float, base: u64, prec: u64, rm: RoundingMode) -> (Float, Ordering) {
    let (log, o) = x.clone().log_base_prec_round(base, prec, rm);
    assert!(log.is_valid());

    let (log_alt, o_alt) = x.log_base_prec_round_ref(base, prec, rm);
    assert!(log_alt.is_valid());
    assert_eq!(ComparableFloatRef(&log_alt), ComparableFloatRef(&log));
    assert_eq!(o_alt, o);

    let mut x_alt = x.clone();
    let o_alt = x_alt.log_base_prec_round_assign(base, prec, rm);
    assert!(x_alt.is_valid());
    assert_eq!(ComparableFloatRef(&x_alt), ComparableFloatRef(&log));
    assert_eq!(o_alt, o);

    // When the base is a power of 2 the result must match log_base_power_of_2.
    if base.is_power_of_2() {
        let (alt, o2) =
            x.log_base_power_of_2_prec_round_ref(i64::from(base.trailing_zeros()), prec, rm);
        assert_eq!(ComparableFloatRef(&alt), ComparableFloatRef(&log));
        assert_eq!(o2, o);
    }

    if log.is_normal() {
        assert_eq!(log.get_prec(), Some(prec));
    }

    // Cross-check against the independent rug oracle, including on extreme inputs: with rational
    // results detected up front (see rational_log_base), the ln(x) / ln(base) bracketing stays
    // bounded and fast even there, matching log_base_2.
    if let Ok(rug_rm) = rug_round_try_from_rounding_mode(rm) {
        let (rug_log, rug_o) =
            rug_log_base_prec_round(&rug::Float::exact_from(x), base, prec, rug_rm);
        assert_eq!(
            ComparableFloatRef(&Float::from(&rug_log)),
            ComparableFloatRef(&log),
        );
        assert_eq!(rug_o, o);
    }
    (log, o)
}

#[test]
fn test_log_base_prec_round() {
    let test = |x: Float, base: u64, prec: u64, rm: RoundingMode, out: &str, o_out: Ordering| {
        let (log, o) = check(&x, base, prec, rm);
        assert_eq!(log.to_string(), out);
        assert_eq!(o, o_out);
    };
    // Exact powers: log_base(base^n) = n.
    test(Float::from(1000), 10, 10, Exact, "3.0", Equal);
    test(Float::from(100), 10, 10, Exact, "2.0", Equal);
    test(Float::from(81), 3, 10, Exact, "4.0", Equal);
    test(Float::from(243), 3, 10, Nearest, "5.0", Equal); // 3^5
    test(Float::from(16807), 7, 10, Nearest, "5.0", Equal); // 7^5
    test(Float::from(1), 10, 10, Exact, "0.0", Equal);
    // Base a power of 2: delegates to log_base_power_of_2.
    test(Float::from(64), 4, 10, Nearest, "3.0", Equal); // log_4(64) = 3
    test(Float::from(8), 2, 10, Exact, "3.0", Equal);
    test(Float::from(1000000), 8, 10, Nearest, "6.64", Less); // log_8(10^6), inexact
    // Perfect-power base: exercises the express_as_power reduction (base 9 -> root 3) and the
    // dyadic exact case.
    test(Float::from(3), 9, 10, Exact, "0.5", Equal); // log_9(3) = 1/2
    test(Float::from(81), 9, 10, Exact, "2.0", Equal); // log_9(81) = 2
    // Inexact.
    test(Float::from(50), 10, 10, Floor, "1.697", Less);
    test(Float::from(50), 10, 10, Ceiling, "1.699", Greater);
    test(Float::from(50), 10, 10, Nearest, "1.699", Greater);
    test(Float::from(2), 3, 20, Nearest, "0.63093", Greater);
    // x in (0, 1): negative result.
    test(Float::from(1) >> 3, 3, 20, Nearest, "-1.89279", Less); // log_3(1/8)
    // Special cases.
    test(Float::NAN, 10, 10, Nearest, "NaN", Equal);
    test(Float::INFINITY, 10, 10, Nearest, "Infinity", Equal);
    test(Float::NEGATIVE_INFINITY, 10, 10, Nearest, "NaN", Equal);
    test(Float::ZERO, 10, 10, Nearest, "-Infinity", Equal);
    test(Float::NEGATIVE_ZERO, 10, 10, Nearest, "-Infinity", Equal);
    test(-Float::from(5), 10, 10, Nearest, "NaN", Equal);
}

#[test]
fn test_log_base_directed_consistency() {
    // For a curated set of inputs, exercise every rounding mode (cross-checked against the oracle
    // inside `check`), and confirm Floor <= Nearest <= Ceiling for finite results.
    let inputs: &[(Float, u64, u64)] = &[
        (Float::from(1000), 10, 30),
        (Float::from(50), 10, 30),
        (Float::from(81), 3, 30),
        (Float::from(2), 3, 30),
        (Float::from(7), 5, 40),
        (Float::from(8), 2, 30),         // power-of-2 base
        (Float::from(64), 4, 30),        // power-of-2 base
        (Float::from(1) >> 5, 3, 30),    // 1/32, negative result
        (Float::from(5) >> 1, 3, 30),    // 2.5, a non-integer >= 1
        (Float::from(3) << 1000, 5, 10), // huge exponent: exercises the balloon-safety guard
    ];
    for (x, base, prec) in inputs {
        let (floor, _) = check(x, *base, *prec, Floor);
        let (ceiling, _) = check(x, *base, *prec, Ceiling);
        let (nearest, _) = check(x, *base, *prec, Nearest);
        let _ = check(x, *base, *prec, Down);
        let _ = check(x, *base, *prec, Up);
        if floor.is_normal() && ceiling.is_normal() {
            assert!(
                floor <= nearest,
                "{x} base {base}: floor {floor} > nearest {nearest}"
            );
            assert!(
                nearest <= ceiling,
                "{x} base {base}: nearest {nearest} > ceiling {ceiling}"
            );
        }
    }
}

#[test]
fn test_log_base() {
    // The `LogBase` trait: rounds to the input's precision, to nearest.
    let test = |x: Float, base: u64, out: &str| {
        let log = x.clone().log_base(base);
        assert!(log.is_valid());
        assert_eq!(log.to_string(), out);

        let log_alt = (&x).log_base(base);
        assert!(log_alt.is_valid());
        assert_eq!(ComparableFloatRef(&log_alt), ComparableFloatRef(&log));

        let mut x_alt = x.clone();
        x_alt.log_base_assign(base);
        assert_eq!(ComparableFloatRef(&x_alt), ComparableFloatRef(&log));

        assert_eq!(
            ComparableFloatRef(&Float::from(&rug_log_base(
                &rug::Float::exact_from(&x),
                base
            ))),
            ComparableFloatRef(&log),
        );
        let _ = x.significant_bits();
    };
    test(Float::from(1000), 10, "3.0");
    test(Float::from(81), 3, "4.0");
    test(Float::from(1), 10, "0.0");
    test(Float::NAN, 10, "NaN");
    test(Float::INFINITY, 10, "Infinity");
    test(Float::ZERO, 10, "-Infinity");
}

#[test]
fn log_base_prec_round_fail() {
    // Precision must be nonzero.
    assert_panic!(Float::from(10).log_base_prec_round(10, 0, Nearest));
    assert_panic!(Float::from(10).log_base_prec_round_ref(10, 0, Nearest));
    // Base must be greater than 1.
    assert_panic!(Float::from(10).log_base_prec_round(1, 10, Nearest));
    assert_panic!(Float::from(10).log_base_prec_round(0, 10, Nearest));
    assert_panic!(Float::from(10).log_base(1));
    // Exact rounding of an inexact result panics.
    assert_panic!(Float::from(50).log_base_prec_round(10, 10, Exact));
    assert_panic!(Float::from(2).log_base_prec_round(3, 10, Exact));
}

// Verifies the `_prec` variant against `_prec_round(..., Nearest)`, plus by-value/ref/assign
// consistency.
fn check_prec(x: &Float, base: u64, prec: u64) {
    let (log, o) = x.clone().log_base_prec(base, prec);
    assert!(log.is_valid());
    let (log_ref, o_ref) = x.log_base_prec_ref(base, prec);
    assert_eq!(ComparableFloatRef(&log_ref), ComparableFloatRef(&log));
    assert_eq!(o_ref, o);
    let mut x_alt = x.clone();
    let o_alt = x_alt.log_base_prec_assign(base, prec);
    assert_eq!(ComparableFloatRef(&x_alt), ComparableFloatRef(&log));
    assert_eq!(o_alt, o);
    let (expected, eo) = check(x, base, prec, Nearest);
    assert_eq!(ComparableFloatRef(&log), ComparableFloatRef(&expected));
    assert_eq!(o, eo);
}

// Verifies the `_round` variant (precision = input precision) against the oracle and the
// `_prec_round` variant, plus by-value/ref/assign consistency.
fn check_round(x: &Float, base: u64, rm: RoundingMode) {
    let prec = x.significant_bits();
    let (expected, eo) = check(x, base, prec, rm);
    let (log, o) = x.clone().log_base_round(base, rm);
    assert!(log.is_valid());
    assert_eq!(ComparableFloatRef(&log), ComparableFloatRef(&expected));
    assert_eq!(o, eo);
    let (log_ref, o_ref) = x.log_base_round_ref(base, rm);
    assert_eq!(ComparableFloatRef(&log_ref), ComparableFloatRef(&log));
    assert_eq!(o_ref, o);
    let mut x_alt = x.clone();
    let o_alt = x_alt.log_base_round_assign(base, rm);
    assert_eq!(ComparableFloatRef(&x_alt), ComparableFloatRef(&log));
    assert_eq!(o_alt, o);
}

#[test]
fn log_base_prec_round_properties() {
    float_unsigned_unsigned_rounding_mode_quadruple_gen_var_5().test_properties(
        |(x, base, prec, rm)| {
            check(&x, base, prec, rm);
        },
    );
    float_unsigned_unsigned_rounding_mode_quadruple_gen_var_6().test_properties(
        |(x, base, prec, rm)| {
            check(&x, base, prec, rm);
        },
    );
}

#[test]
fn log_base_prec_properties() {
    float_unsigned_unsigned_rounding_mode_quadruple_gen_var_5().test_properties(
        |(x, base, prec, _rm)| {
            check_prec(&x, base, prec);
        },
    );
    float_unsigned_unsigned_rounding_mode_quadruple_gen_var_6().test_properties(
        |(x, base, prec, _rm)| {
            check_prec(&x, base, prec);
        },
    );
}

#[test]
fn log_base_round_properties() {
    float_unsigned_rounding_mode_triple_gen_var_27().test_properties(|(x, base, rm)| {
        check_round(&x, base, rm);
    });
    float_unsigned_rounding_mode_triple_gen_var_28().test_properties(|(x, base, rm)| {
        check_round(&x, base, rm);
    });
}

#[test]
fn log_base_properties() {
    let f = |x: Float, base: u64| {
        let prec = x.significant_bits();
        let (expected, _) = check(&x, base, prec, Nearest);
        let log = x.clone().log_base(base);
        assert!(log.is_valid());
        assert_eq!(ComparableFloatRef(&log), ComparableFloatRef(&expected));
        let log_ref = (&x).log_base(base);
        assert_eq!(ComparableFloatRef(&log_ref), ComparableFloatRef(&log));
        let mut x_alt = x.clone();
        x_alt.log_base_assign(base);
        assert_eq!(ComparableFloatRef(&x_alt), ComparableFloatRef(&log));
    };
    float_unsigned_rounding_mode_triple_gen_var_27().test_properties(|(x, base, _rm)| {
        f(x, base);
    });
    float_unsigned_rounding_mode_triple_gen_var_28().test_properties(|(x, base, _rm)| {
        f(x, base);
    });
}

#[allow(clippy::needless_pass_by_value)]
fn log_base_rational_prec_round_properties_helper(
    x: Rational,
    base: u64,
    prec: u64,
    rm: RoundingMode,
) {
    let (log, o) = Float::log_base_rational_prec_round(x.clone(), base, prec, rm);
    assert!(log.is_valid());

    let (log_alt, o_alt) = Float::log_base_rational_prec_round_ref(&x, base, prec, rm);
    assert!(log_alt.is_valid());
    assert_eq!(ComparableFloatRef(&log_alt), ComparableFloatRef(&log));
    assert_eq!(o_alt, o);

    // Power-of-2 base must agree with log_base_power_of_2_rational.
    if base.is_power_of_2() {
        let (alt, o2) = Float::log_base_power_of_2_rational_prec_round_ref(
            &x,
            i64::from(base.trailing_zeros()),
            prec,
            rm,
        );
        assert_eq!(ComparableFloatRef(&alt), ComparableFloatRef(&log));
        assert_eq!(o2, o);
    }

    // Independent rug oracle (skips Exact, which rug can't represent).
    if let Ok(rug_rm) = rug_round_try_from_rounding_mode(rm) {
        let (rug_log, rug_o) = rug_log_base_rational_prec_round(&x, base, prec, rug_rm);
        assert_eq!(
            ComparableFloatRef(&Float::from(&rug_log)),
            ComparableFloatRef(&log),
        );
        assert_eq!(rug_o, o);
    }

    // log_base(1/x) = -log_base(x), with the rounding direction reversed.
    if x != 0u32 {
        let (log_recip, o_recip) =
            Float::log_base_rational_prec_round((&x).reciprocal(), base, prec, -rm);
        assert!(log_recip.is_valid());
        assert_eq!(
            ComparableFloatRef(&(-log_recip).abs_negative_zero()),
            ComparableFloatRef(&log.abs_negative_zero_ref())
        );
        assert_eq!(o_recip.reverse(), o);
    }

    if log.is_normal() {
        assert_eq!(log.get_prec(), Some(prec));
        if x > 1u32 && o > Less {
            assert!(log > 0u32);
        } else if x > 0u32 && x < 1u32 && o < Greater {
            assert!(log < 0u32);
        }
    }
}

#[test]
fn log_base_rational_prec_round_properties() {
    rational_unsigned_unsigned_rounding_mode_quadruple_gen_var_1().test_properties(
        |(x, base, prec, rm)| {
            log_base_rational_prec_round_properties_helper(x, base, prec, rm);
        },
    );

    let mut config = GenConfig::new();
    config.insert("mean_bits_n", 2048);
    config.insert("mean_stripe_n", 16 << Limb::LOG_WIDTH);
    rational_unsigned_unsigned_rounding_mode_quadruple_gen_var_1().test_properties_with_config(
        &config,
        |(x, base, prec, rm)| {
            log_base_rational_prec_round_properties_helper(x, base, prec, rm);
        },
    );
}

#[test]
fn log_base_rational_prec_properties() {
    rational_unsigned_unsigned_rounding_mode_quadruple_gen_var_1().test_properties(
        |(x, base, prec, _rm)| {
            let (log, o) = Float::log_base_rational_prec(x.clone(), base, prec);
            assert!(log.is_valid());
            let (log_ref, o_ref) = Float::log_base_rational_prec_ref(&x, base, prec);
            assert_eq!(ComparableFloatRef(&log_ref), ComparableFloatRef(&log));
            assert_eq!(o_ref, o);
            // _prec is _prec_round with Nearest.
            let (expected, eo) = Float::log_base_rational_prec_round(x, base, prec, Nearest);
            assert_eq!(ComparableFloatRef(&log), ComparableFloatRef(&expected));
            assert_eq!(o, eo);
        },
    );
}

#[test]
fn test_log_base_rational_prec_round() {
    let test =
        |n: i64, d: u64, base: u64, prec: u64, rm: RoundingMode, out: &str, o_out: Ordering| {
            let x = Rational::from_signeds(n, i64::exact_from(d));
            let (log, o) = Float::log_base_rational_prec_round(x.clone(), base, prec, rm);
            assert!(log.is_valid());
            assert_eq!(log.to_string(), out);
            assert_eq!(o, o_out);
            let (log_alt, o_alt) = Float::log_base_rational_prec_round_ref(&x, base, prec, rm);
            assert_eq!(ComparableFloatRef(&log_alt), ComparableFloatRef(&log));
            assert_eq!(o_alt, o);
            if let Ok(rug_rm) = rug_round_try_from_rounding_mode(rm) {
                let (rl, ro) = rug_log_base_rational_prec_round(&x, base, prec, rug_rm);
                assert_eq!(
                    ComparableFloatRef(&Float::from(&rl)),
                    ComparableFloatRef(&log)
                );
                assert_eq!(ro, o);
            }
        };
    test(3, 5, 10, 20, Floor, "-0.221849", Less);
    test(3, 5, 10, 20, Ceiling, "-0.2218487", Greater);
    test(3, 5, 10, 20, Nearest, "-0.2218487", Greater);
    test(2, 1, 3, 20, Floor, "0.630929", Less);
    test(2, 1, 3, 20, Ceiling, "0.63093", Greater);
    test(2, 1, 3, 20, Nearest, "0.63093", Greater);
    test(1, 8, 3, 20, Floor, "-1.89279", Less);
    test(1, 8, 3, 20, Ceiling, "-1.892788", Greater);
    test(1, 8, 3, 20, Nearest, "-1.89279", Less);
    test(7, 1, 5, 30, Floor, "1.209061954", Less);
    test(7, 1, 5, 30, Ceiling, "1.209061956", Greater);
    test(7, 1, 5, 30, Nearest, "1.209061956", Greater);
    test(50, 1, 10, 10, Floor, "1.697", Less);
    test(50, 1, 10, 10, Ceiling, "1.699", Greater);
    test(50, 1, 10, 10, Nearest, "1.699", Greater);
    test(1, 100, 10, 10, Floor, "-2.0", Equal);
    test(1, 100, 10, 10, Ceiling, "-2.0", Equal);
    test(1, 100, 10, 10, Nearest, "-2.0", Equal);
    test(1, 100, 10, 10, Exact, "-2.0", Equal);
    test(22, 7, 3, 15, Floor, "1.0423", Less);
    test(22, 7, 3, 15, Ceiling, "1.04236", Greater);
    test(22, 7, 3, 15, Nearest, "1.04236", Greater);
    test(3, 1, 9, 10, Floor, "0.5", Equal);
    test(3, 1, 9, 10, Ceiling, "0.5", Equal);
    test(3, 1, 9, 10, Nearest, "0.5", Equal);
    test(3, 1, 9, 10, Exact, "0.5", Equal);
    test(1, 9, 3, 10, Floor, "-2.0", Equal);
    test(1, 9, 3, 10, Ceiling, "-2.0", Equal);
    test(1, 9, 3, 10, Nearest, "-2.0", Equal);
    test(1, 9, 3, 10, Exact, "-2.0", Equal);
    test(1000, 1, 10, 10, Floor, "3.0", Equal);
    test(1000, 1, 10, 10, Ceiling, "3.0", Equal);
    test(1000, 1, 10, 10, Nearest, "3.0", Equal);
    test(1000, 1, 10, 10, Exact, "3.0", Equal);
    test(5, 2, 4, 20, Floor, "0.660964", Less);
    test(5, 2, 4, 20, Ceiling, "0.660965", Greater);
    test(5, 2, 4, 20, Nearest, "0.660964", Less);
    // Special cases.
    test(0, 1, 10, 10, Nearest, "-Infinity", Equal);
    test(0, 1, 10, 10, Exact, "-Infinity", Equal);
    test(-3, 1, 10, 10, Nearest, "NaN", Equal);
    test(1, 1, 10, 10, Exact, "0.0", Equal);
}

#[test]
fn test_log_base_rational_prec() {
    let test = |n: i64, d: u64, base: u64, prec: u64, out: &str, o_out: Ordering| {
        let x = Rational::from_signeds(n, i64::exact_from(d));
        let (log, o) = Float::log_base_rational_prec(x.clone(), base, prec);
        assert!(log.is_valid());
        assert_eq!(log.to_string(), out);
        assert_eq!(o, o_out);
        let (log_alt, o_alt) = Float::log_base_rational_prec_ref(&x, base, prec);
        assert_eq!(ComparableFloatRef(&log_alt), ComparableFloatRef(&log));
        assert_eq!(o_alt, o);
    };
    test(3, 5, 10, 20, "-0.2218487", Greater);
    test(2, 1, 3, 20, "0.63093", Greater);
    test(1, 8, 3, 20, "-1.89279", Less);
    test(7, 1, 5, 30, "1.209061956", Greater);
    test(50, 1, 10, 10, "1.699", Greater);
    test(1, 100, 10, 10, "-2.0", Equal);
    test(22, 7, 3, 15, "1.04236", Greater);
    test(3, 1, 9, 10, "0.5", Equal);
    test(1, 9, 3, 10, "-2.0", Equal);
    test(1000, 1, 10, 10, "3.0", Equal);
    test(5, 2, 4, 20, "0.660964", Less);
    test(0, 1, 10, 10, "-Infinity", Equal);
    test(1, 1, 10, 10, "0.0", Equal);
}

#[test]
#[allow(clippy::type_repetition_in_bounds)]
fn test_primitive_float_log_base() {
    fn test<T: PrimitiveFloat>(x: T, base: u64, out: T)
    where
        Float: From<T> + PartialOrd<T>,
        for<'a> T: ExactFrom<&'a Float> + RoundingFrom<&'a Float>,
    {
        assert_eq!(NiceFloat(primitive_float_log_base(x, base)), NiceFloat(out));
    }
    test::<f32>(f32::NAN, 10, f32::NAN);
    test::<f32>(f32::INFINITY, 10, f32::INFINITY);
    test::<f32>(f32::NEGATIVE_INFINITY, 10, f32::NAN);
    test::<f32>(0.0, 10, f32::NEGATIVE_INFINITY);
    test::<f32>(-0.0, 10, f32::NEGATIVE_INFINITY);
    test::<f32>(1.0, 10, 0.0);
    test::<f32>(100.0, 10, 2.0); // log_10(100)
    test::<f32>(1000.0, 10, 3.0); // log_10(1000)
    test::<f32>(8.0, 2, 3.0); // log_2(8), power-of-2 base
    test::<f32>(9.0, 3, 2.0); // log_3(9)
    test::<f32>(50.0, 10, 1.6989699602127075); // log_10(50)
    test::<f32>(2.0, 10, std::f32::consts::LOG10_2); // log_10(2)
    test::<f32>(50.0, 7, 2.0103821754455566); // log_7(50)
    test::<f32>(-1.0, 10, f32::NAN);

    test::<f64>(f64::NAN, 10, f64::NAN);
    test::<f64>(f64::INFINITY, 10, f64::INFINITY);
    test::<f64>(f64::NEGATIVE_INFINITY, 10, f64::NAN);
    test::<f64>(0.0, 10, f64::NEGATIVE_INFINITY);
    test::<f64>(-0.0, 10, f64::NEGATIVE_INFINITY);
    test::<f64>(1.0, 10, 0.0);
    test::<f64>(100.0, 10, 2.0); // log_10(100)
    test::<f64>(1000.0, 10, 3.0); // log_10(1000)
    test::<f64>(8.0, 2, 3.0); // log_2(8), power-of-2 base
    test::<f64>(9.0, 3, 2.0); // log_3(9)
    test::<f64>(50.0, 10, 1.6989700043360187); // log_10(50)
    test::<f64>(2.0, 10, std::f64::consts::LOG10_2); // log_10(2)
    test::<f64>(50.0, 7, 2.0103821378018543); // log_7(50)
    test::<f64>(-1.0, 10, f64::NAN);
}

#[allow(clippy::type_repetition_in_bounds)]
fn primitive_float_log_base_properties_helper<T: PrimitiveFloat>()
where
    Float: From<T> + PartialOrd<T>,
    for<'a> T: ExactFrom<&'a Float> + RoundingFrom<&'a Float>,
{
    primitive_float_unsigned_pair_gen_var_4::<T, u64>().test_properties(|(x, base)| {
        if base < 2 {
            return;
        }
        let y = primitive_float_log_base(x, base);
        // When the base is a power of 2, log_b agrees with log_base_power_of_2.
        if base.is_power_of_2() {
            assert_eq!(
                NiceFloat(y),
                NiceFloat(primitive_float_log_base_power_of_2(
                    x,
                    i64::exact_from(base.trailing_zeros())
                ))
            );
        }
    });
}

#[test]
fn primitive_float_log_base_properties() {
    apply_fn_to_primitive_floats!(primitive_float_log_base_properties_helper);
}

#[test]
#[allow(clippy::type_repetition_in_bounds)]
fn test_primitive_float_log_base_rational() {
    fn test<T: PrimitiveFloat>(s: &str, base: u64, out: T)
    where
        Float: From<T> + PartialOrd<T>,
        for<'a> T: ExactFrom<&'a Float> + RoundingFrom<&'a Float>,
    {
        let u = Rational::from_str(s).unwrap();
        assert_eq!(
            NiceFloat(primitive_float_log_base_rational(&u, base)),
            NiceFloat(out)
        );
    }
    test::<f32>("0", 10, f32::NEGATIVE_INFINITY);
    test::<f32>("1", 10, 0.0);
    test::<f32>("1000", 10, 3.0); // log_10(1000)
    test::<f32>("1/1000", 10, -3.0); // log_10(1/1000)
    test::<f32>("9", 3, 2.0); // log_3(9)
    test::<f32>("1/9", 3, -2.0); // log_3(1/9)
    test::<f32>("8", 2, 3.0); // log_2(8), power-of-2 base
    test::<f32>("50", 10, 1.6989699602127075); // log_10(50)
    test::<f32>("1/3", 10, -0.4771212637424469); // log_10(1/3)
    test::<f32>("-1", 10, f32::NAN);
    test::<f32>("-22/7", 10, f32::NAN);

    test::<f64>("0", 10, f64::NEGATIVE_INFINITY);
    test::<f64>("1", 10, 0.0);
    test::<f64>("1000", 10, 3.0); // log_10(1000)
    test::<f64>("1/1000", 10, -3.0); // log_10(1/1000)
    test::<f64>("9", 3, 2.0); // log_3(9)
    test::<f64>("1/9", 3, -2.0); // log_3(1/9)
    test::<f64>("8", 2, 3.0); // log_2(8), power-of-2 base
    test::<f64>("50", 10, 1.6989700043360187); // log_10(50)
    test::<f64>("1/3", 10, -0.47712125471966244); // log_10(1/3)
    test::<f64>("-1", 10, f64::NAN);
    test::<f64>("-22/7", 10, f64::NAN);
}

#[allow(clippy::type_repetition_in_bounds)]
fn primitive_float_log_base_rational_properties_helper<T: PrimitiveFloat>()
where
    Float: From<T> + PartialOrd<T>,
    for<'a> T: ExactFrom<&'a Float> + RoundingFrom<&'a Float>,
{
    // The generator yields a positive `Rational` and a `u64` greater than 1.
    rational_unsigned_pair_gen_var_9::<u64>().test_properties(|(x, base)| {
        let y = primitive_float_log_base_rational::<T>(&x, base);
        // When the base is a power of 2, log_b agrees with log_base_power_of_2.
        if base.is_power_of_2() {
            assert_eq!(
                NiceFloat(y),
                NiceFloat(primitive_float_log_base_power_of_2_rational::<T>(
                    &x,
                    i64::exact_from(base.trailing_zeros())
                ))
            );
        }
    });
}

#[test]
fn primitive_float_log_base_rational_properties() {
    apply_fn_to_primitive_floats!(primitive_float_log_base_rational_properties_helper);
}
