Skip to content

Commit

Permalink
Try to fix no_std builds without default features
Browse files Browse the repository at this point in the history
This required making more code dependent on the "std" or "libm" feature.
Also, the documentation of features on docs.rs was improved.

Technically, this is a breaking change, although it should only break
targets that were broken anyway.
  • Loading branch information
vks committed Apr 29, 2021
1 parent 223cd77 commit d37eae8
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 37 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ include = ["src/**/*", "benches/*", "LICENSE-*", "README.md"]
[features]
serde1 = ["serde", "serde_derive", "serde-big-array"]
nightly = []
std = ["easy-cast/std"]
libm = ["easy-cast/libm"]
std = ["easy-cast/std", "num-traits/std"]
libm = ["easy-cast/libm", "num-traits/libm"]
default = ["libm"]

[[bench]]
Expand Down Expand Up @@ -52,3 +52,7 @@ proptest = "0.10"
# byteorder is not a direct dependency, but the MSRV of 1.4 is higher than ours.
# Therefore, we have to enforce version 1.3.
byteorder = "=1.3"

[package.metadata.docs.rs]
# Enable certain features when building docs for docs.rs
features = ["libm", "serde1", "rayon"]
11 changes: 8 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,23 @@
mod weighted_mean;
mod minmax;
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
mod quantile;
mod traits;
#[macro_use] mod histogram;
#[cfg(feature = "nightly")]
#[cfg_attr(doc_cfg, feature(nightly))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "nightly")))]
pub mod histogram_const;

pub use crate::moments::{Mean, Variance, Skewness, Kurtosis, MeanWithError};
pub use crate::moments::{Mean, Variance, MeanWithError};
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
pub use crate::moments::{Skewness, Kurtosis};

pub use crate::weighted_mean::{WeightedMean, WeightedMeanWithError};
pub use crate::minmax::{Min, Max};
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, any(feature(std), feature(libm)))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
pub use crate::quantile::Quantile;
pub use crate::traits::{Estimate, Merge, Histogram};
pub use crate::histogram::{InvalidRangeError, SampleOutOfRangeError};
Expand Down
2 changes: 2 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ macro_rules! impl_from_iterator {
macro_rules! impl_from_par_iterator {
( $name:ident ) => {
#[cfg(feature = "rayon")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "rayon")))]
impl ::rayon::iter::FromParallelIterator<f64> for $name {
fn from_par_iter<I>(par_iter: I) -> $name
where I: ::rayon::iter::IntoParallelIterator<Item = f64>,
Expand All @@ -204,6 +205,7 @@ macro_rules! impl_from_par_iterator {
}

#[cfg(feature = "rayon")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "rayon")))]
impl<'a> ::rayon::iter::FromParallelIterator<&'a f64> for $name {
fn from_par_iter<I>(par_iter: I) -> $name
where I: ::rayon::iter::IntoParallelIterator<Item = &'a f64>,
Expand Down
21 changes: 17 additions & 4 deletions src/moments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use super::{Estimate, Merge};

include!("mean.rs");
include!("variance.rs");
#[cfg(any(feature = "std", feature = "libm"))]
include!("skewness.rs");
#[cfg(any(feature = "std", feature = "libm"))]
include!("kurtosis.rs");

/// Alias for `Variance`.
Expand Down Expand Up @@ -100,6 +102,8 @@ macro_rules! define_moments_common {
}

/// Estimate the `p`th standardized moment of the population.
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
#[inline]
pub fn standardized_moment(&self, p: usize) -> f64 {
match p {
Expand All @@ -109,7 +113,8 @@ macro_rules! define_moments_common {
_ => {
let variance = self.central_moment(2);
assert_ne!(variance, 0.);
self.central_moment(p) / pow(variance.sqrt(), p)
self.central_moment(p) / pow(
num_traits::Float::sqrt(variance), p)
},
}
}
Expand All @@ -126,6 +131,8 @@ macro_rules! define_moments_common {
}

/// Calculate the sample skewness.
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
#[inline]
pub fn sample_skewness(&self) -> f64 {
if self.n < 2 {
Expand All @@ -135,11 +142,17 @@ macro_rules! define_moments_common {
if self.n < 3 {
// Method of moments
return self.central_moment(3) /
(n * (self.central_moment(2) / (n - 1.)).powf(1.5))
num_traits::Float::powf(
n * (self.central_moment(2) / (n - 1.)), 1.5
)
}
// Adjusted Fisher-Pearson standardized moment coefficient
(n * (n - 1.)).sqrt() / (n * (n - 2.)) *
self.central_moment(3) / (self.central_moment(2) / n).powf(1.5)
num_traits::Float::sqrt(n * (n - 1.)) /
(n * (n - 2.)) *
num_traits::Float::powf(
self.central_moment(3) / (self.central_moment(2) / n),
1.5
)
}

/// Calculate the sample excess kurtosis.
Expand Down
4 changes: 3 additions & 1 deletion src/moments/skewness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// numbers ("population").
///
/// This can be used to estimate the standard error of the mean.
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Skewness {
Expand Down Expand Up @@ -97,7 +98,8 @@ impl Skewness {
let n = self.len().to_f64().unwrap();
let sum_2 = self.avg.sum_2;
debug_assert_ne!(sum_2, 0.);
n.sqrt() * self.sum_3 / (sum_2*sum_2*sum_2).sqrt()
num_traits::Float::sqrt(n) * self.sum_3
/ num_traits::Float::sqrt(sum_2*sum_2*sum_2)
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/moments/variance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,22 @@ impl Variance {
self.sum_2 / n.to_f64().unwrap()
}

/// Estimate the standard error of the mean of the population.
/// Estimate the variance of the mean of the population.
#[inline]
pub fn error(&self) -> f64 {
pub fn variance_of_mean(&self) -> f64 {
let n = self.avg.len();
if n == 0 {
return 0.;
}
(self.sample_variance() / n.to_f64().unwrap()).sqrt()
self.sample_variance() / n.to_f64().unwrap()
}

/// Estimate the standard error of the mean of the population.
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
#[inline]
pub fn error(&self) -> f64 {
num_traits::Float::sqrt(self.variance_of_mean())
}

}
Expand Down
2 changes: 1 addition & 1 deletion src/quantile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use super::Estimate;
/// [1]: http:https://www.cs.wustl.edu/~jain/papers/ftp/psqr.pdf
/// [2]: https://crates.io/crates/quantiles
#[derive(Debug, Clone)]
#[cfg_attr(doc_cfg, any(feature(std), feature(libm)))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Quantile {
/// Marker heights.
Expand Down
18 changes: 16 additions & 2 deletions src/weighted_mean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,17 +259,31 @@ impl WeightedMeanWithError {
/// This unbiased estimator assumes that the samples were independently
/// drawn from the same population with constant variance.
#[inline]
pub fn error(&self) -> f64 {
pub fn variance_of_weighted_mean(&self) -> f64 {
// This uses the same estimate as WinCross, which should provide better
// results than the ones used by SPSS or Mentor.
//
// See http:https://www.analyticalgroup.com/download/WEIGHTED_VARIANCE.pdf.

let weight_sum = self.weighted_avg.sum_weights();
if weight_sum == 0. {
return 0.;
}
let inv_effective_len = self.weight_sum_sq / (weight_sum * weight_sum);
(self.sample_variance() * inv_effective_len).sqrt()
self.sample_variance() * inv_effective_len
}

/// Estimate the standard error of the *weighted* mean of the population.
///
/// Returns 0 if the sum of weights is 0.
///
/// This unbiased estimator assumes that the samples were independently
/// drawn from the same population with constant variance.
#[cfg(any(feature = "std", feature = "libm"))]
#[cfg_attr(doc_cfg, doc(cfg(any(feature = "std", feature = "libm"))))]
#[inline]
pub fn error(&self) -> f64 {
num_traits::Float::sqrt(self.variance_of_weighted_mean())
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod histogram;
#[cfg(feature = "nightly")]
mod histogram_const;
#[cfg(any(feature = "std", feature = "libm"))]
mod kurtosis;
mod macros;
mod max;
Expand All @@ -13,7 +14,9 @@ mod moments;
mod proptest;
#[cfg(any(feature = "std", feature = "libm"))]
mod quantile;
#[cfg(any(feature = "std", feature = "libm"))]
mod random;
#[cfg(any(feature = "std", feature = "libm"))]
mod skewness;
mod streaming_stats;
mod weighted_mean;
11 changes: 10 additions & 1 deletion tests/integration/mean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ fn trivial() {
assert_eq!(a.len(), 1);
assert_eq!(a.sample_variance(), 0.0);
assert_eq!(a.population_variance(), 0.0);
assert_eq!(a.variance_of_mean(), 0.0);
#[cfg(any(feature = "std", feature = "libm"))]
assert_eq!(a.error(), 0.0);
a.add(1.0);
assert_eq!(a.mean(), 1.0);
assert_eq!(a.len(), 2);
assert_eq!(a.sample_variance(), 0.0);
assert_eq!(a.population_variance(), 0.0);
assert_eq!(a.variance_of_mean(), 0.0);
#[cfg(any(feature = "std", feature = "libm"))]
assert_eq!(a.error(), 0.0);
}

Expand All @@ -28,7 +32,8 @@ fn simple() {
assert_eq!(a.mean(), 3.0);
assert_eq!(a.len(), 5);
assert_eq!(a.sample_variance(), 2.5);
assert_almost_eq!(a.error(), f64::sqrt(0.5), 1e-16);
#[cfg(any(feature = "std", feature = "libm"))]
assert_almost_eq!(a.error(), num_traits::Float::sqrt(0.5), 1e-16);
}

#[cfg(feature = "serde1")]
Expand All @@ -41,6 +46,8 @@ fn simple_serde() {
assert_eq!(c.mean(), 3.0);
assert_eq!(c.len(), 5);
assert_eq!(c.sample_variance(), 2.5);
assert_eq!(c.variance_of_mean(), 0.5);
#[cfg(any(feature = "std", feature = "libm"))]
assert_almost_eq!(c.error(), f64::sqrt(0.5), 1e-16);
}

Expand All @@ -53,6 +60,8 @@ fn simple_rayon() {
assert_eq!(a.mean(), 3.0);
assert_eq!(a.len(), 5);
assert_eq!(a.sample_variance(), 2.5);
assert_eq!(c.variance_of_mean(), 0.5);
#[cfg(any(feature = "std", feature = "libm"))]
assert_almost_eq!(a.error(), f64::sqrt(0.5), 1e-16);
}

Expand Down
45 changes: 25 additions & 20 deletions tests/integration/moments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn trivial() {
let mut a = Moments4::new();
assert_eq!(a.len(), 0);
assert_eq!(a.central_moment(1), 0.0);
#[cfg(any(feature = "std", feature = "libm"))]
assert_eq!(a.standardized_moment(2), 1.0);
a.add(1.0);
assert_eq!(a.len(), 1);
Expand All @@ -35,16 +36,18 @@ fn simple() {
assert_eq!(a.central_moment(1), 0.0);
// variance
assert_eq!(a.central_moment(2), 2.0);
assert_eq!(a.standardized_moment(0), 5.0);
assert_eq!(a.standardized_moment(1), 0.0);
assert_eq!(a.standardized_moment(2), 1.0);
assert_almost_eq!(a.sample_skewness(), 0.0, 1e-15);
assert_almost_eq!(a.standardized_moment(3), 0.0, 1e-15);
a.add(1.0);
// skewness
assert_almost_eq!(a.standardized_moment(3), 0.2795084971874741, 1e-15);
// kurtosis
assert_almost_eq!(a.standardized_moment(4), -1.365 + 3.0, 1e-14);
#[cfg(any(feature = "std", feature = "libm"))] {
assert_eq!(a.standardized_moment(0), 5.0);
assert_eq!(a.standardized_moment(1), 0.0);
assert_eq!(a.standardized_moment(2), 1.0);
assert_almost_eq!(a.sample_skewness(), 0.0, 1e-15);
assert_almost_eq!(a.standardized_moment(3), 0.0, 1e-15);
a.add(1.0);
// skewness
assert_almost_eq!(a.standardized_moment(3), 0.2795084971874741, 1e-15);
// kurtosis
assert_almost_eq!(a.standardized_moment(4), -1.365 + 3.0, 1e-14);
}
}

#[cfg(feature = "serde1")]
Expand All @@ -60,16 +63,18 @@ fn simple_serde() {
assert_eq!(c.central_moment(1), 0.0);
// variance
assert_eq!(c.central_moment(2), 2.0);
assert_eq!(c.standardized_moment(0), 5.0);
assert_eq!(c.standardized_moment(1), 0.0);
assert_eq!(c.standardized_moment(2), 1.0);
assert_almost_eq!(c.sample_skewness(), 0.0, 1e-15);
assert_almost_eq!(c.standardized_moment(3), 0.0, 1e-15);
c.add(1.0);
// skewness
assert_almost_eq!(c.standardized_moment(3), 0.2795084971874741, 1e-15);
// kurtosis
assert_almost_eq!(c.standardized_moment(4), -1.365 + 3.0, 1e-14);
#[cfg(any(feature = "std", feature = "libm"))] {
assert_eq!(c.standardized_moment(0), 5.0);
assert_eq!(c.standardized_moment(1), 0.0);
assert_eq!(c.standardized_moment(2), 1.0);
assert_almost_eq!(c.sample_skewness(), 0.0, 1e-15);
assert_almost_eq!(c.standardized_moment(3), 0.0, 1e-15);
c.add(1.0);
// skewness
assert_almost_eq!(c.standardized_moment(3), 0.2795084971874741, 1e-15);
// kurtosis
assert_almost_eq!(c.standardized_moment(4), -1.365 + 3.0, 1e-14);
}
}

#[test]
Expand Down
11 changes: 11 additions & 0 deletions tests/integration/weighted_mean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ fn trivial() {
assert_eq!(a.sum_weights(), 1.0);
assert_eq!(a.sum_weights_sq(), 1.0);
assert_eq!(a.population_variance(), 0.0);
assert_eq!(a.variance_of_weighted_mean(), 0.0);
#[cfg(any(feature = "std", feature = "libm"))]
assert_eq!(a.error(), 0.0);
a.add(1.0, 1.0);
assert_eq!(a.len(), 2);
Expand All @@ -25,6 +27,8 @@ fn trivial() {
assert_eq!(a.sum_weights(), 2.0);
assert_eq!(a.sum_weights_sq(), 2.0);
assert_eq!(a.population_variance(), 0.0);
assert_eq!(a.variance_of_weighted_mean(), 0.0);
#[cfg(any(feature = "std", feature = "libm"))]
assert_eq!(a.error(), 0.0);
}

Expand All @@ -36,6 +40,8 @@ fn simple() {
assert_eq!(a.unweighted_mean(), 3.0);
assert_eq!(a.sum_weights(), 5.0);
assert_eq!(a.sample_variance(), 2.5);
assert_eq!(a.variance_of_weighted_mean(), 0.5);
#[cfg(any(feature = "std", feature = "libm"))]
assert_almost_eq!(a.error(), f64::sqrt(0.5), 1e-16);
}

Expand All @@ -51,6 +57,8 @@ fn simple_serde() {
assert_eq!(c.unweighted_mean(), 3.0);
assert_eq!(c.sum_weights(), 5.0);
assert_eq!(c.sample_variance(), 2.5);
assert_eq!(a.variance_of_weighted_mean(), 0.5);
#[cfg(any(feature = "std", feature = "libm"))]
assert_almost_eq!(c.error(), f64::sqrt(0.5), 1e-16);
}

Expand All @@ -66,9 +74,12 @@ fn reference() {
assert_eq!(a.sum_weights(), 10.47);
assert_eq!(a.len(), 10);
assert_almost_eq!(a.effective_len(), 8.2315, 1e-4);
assert_almost_eq!(a.variance_of_weighted_mean(), 0.2173, 1e-4);
#[cfg(any(feature = "std", feature = "libm"))]
assert_almost_eq!(a.error(), f64::sqrt(0.2173), 1e-4);
}

#[cfg(any(feature = "std", feature = "libm"))]
#[test]
fn error_corner_case() {
let values = &[1., 2.];
Expand Down

0 comments on commit d37eae8

Please sign in to comment.