vader-parity 0.1.0

VADER sentiment scoring with exact output parity against Python vaderSentiment 3.3.2. Algorithm and lexicons by Hutto and Gilbert (ICWSM-14).
Documentation
//! Rounding with CPython semantics.

/// Round `value` to `digits` decimal places the way CPython's `round(x, n)` does:
/// the exact binary value is rounded to `n` decimal digits, correctly rounded with
/// ties to even on the decimal digit, and the result is the nearest `f64` to that
/// decimal. This is not `(x * 10^n).round() / 10^n`, and the difference shows up
/// in this crate's output; the tie cases are pinned by `tie_cases_match_cpython`,
/// and the harness sweeps 10^6 random doubles against Python's `round`.
///
/// Rust's fixed-precision float formatting performs exactly that decimal rounding
/// (correctly rounded, ties to even, over the exact binary value), and `parse`
/// returns the nearest `f64`, so the composition is CPython's `round`.
pub(crate) fn python_round(value: f64, digits: usize) -> f64 {
    // The fallback cannot be reached: fixed-point formatting of a finite f64
    // always yields a parseable decimal, and every value this crate rounds is
    // finite by construction.
    format!("{value:.digits$}").parse::<f64>().unwrap_or(value)
}

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

    #[test]
    fn tie_cases_match_cpython() {
        // Python: round(0.0625, 3) == 0.062 (exact tie, half to even)
        assert_eq!(python_round(0.0625, 3).to_bits(), 0.062_f64.to_bits());
        // Python: round(2.675, 2) == 2.67 (2.675 is below the tie in binary)
        assert_eq!(python_round(2.675, 2).to_bits(), 2.67_f64.to_bits());
        // Python: round(0.25, 1) == 0.2, round(0.75, 1) == 0.8
        assert_eq!(python_round(0.25, 1).to_bits(), 0.2_f64.to_bits());
        assert_eq!(python_round(0.75, 1).to_bits(), 0.8_f64.to_bits());
        // Python: round(0.00005, 4) == 0.0001 (binary value sits above the tie)
        assert_eq!(python_round(0.000_05, 4).to_bits(), 0.0001_f64.to_bits());
    }

    #[test]
    fn negative_zero_is_preserved() {
        // Python: round(-0.00004, 3) == -0.0
        let rounded = python_round(-0.000_04, 3);
        assert_eq!(rounded.to_bits(), (-0.0_f64).to_bits());
    }

    #[test]
    fn passthrough_of_representable_values() {
        assert_eq!(python_round(0.4404, 4).to_bits(), 0.4404_f64.to_bits());
        assert_eq!(python_round(-0.6597, 4).to_bits(), (-0.6597_f64).to_bits());
        assert_eq!(python_round(0.0, 4).to_bits(), 0.0_f64.to_bits());
        assert_eq!(python_round(1.0, 4).to_bits(), 1.0_f64.to_bits());
        assert_eq!(python_round(-1.0, 4).to_bits(), (-1.0_f64).to_bits());
    }
}