Refactor - Use floats for math and cvt'ing from JS numbers, integer division truncates (#2032)
This commit is contained in:
parent
0c033318fc
commit
a81d427c02
15 changed files with 88 additions and 74 deletions
|
@ -310,12 +310,22 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn div_basic() {
|
||||
fn div_int() {
|
||||
let one = Value::from(5);
|
||||
let two = Value::from(4);
|
||||
let res = div(one, two);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap();
|
||||
assert_eq!("1", format!("{}", out));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_float() {
|
||||
let one = Value::from(5.0);
|
||||
let two = Value::from(4.0);
|
||||
let res = div(one, two);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap();
|
||||
assert_eq!("1.25", format!("{}", out));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,8 +31,7 @@ impl<'js> FromJs<'js> for Value {
|
|||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
val if val.is_int() => Ok(val.as_int().unwrap().into()),
|
||||
val if val.is_float() => Ok(val.as_float().unwrap().into()),
|
||||
val if val.is_number() => Ok(val.as_number().unwrap().into()),
|
||||
val if val.is_array() => {
|
||||
let v = val.as_array().unwrap();
|
||||
let mut x = Array::with_capacity(v.len());
|
||||
|
|
|
@ -3,11 +3,11 @@ use crate::sql::number::Number;
|
|||
|
||||
pub trait Deviation {
|
||||
/// Population Standard Deviation
|
||||
fn deviation(self, sample: bool) -> Number;
|
||||
fn deviation(self, sample: bool) -> f64;
|
||||
}
|
||||
|
||||
impl Deviation for Vec<Number> {
|
||||
fn deviation(self, sample: bool) -> Number {
|
||||
fn deviation(self, sample: bool) -> f64 {
|
||||
self.variance(sample).sqrt()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ use crate::sql::number::{Number, Sorted};
|
|||
pub trait Interquartile {
|
||||
/// Interquartile Range - the difference between the upper and lower quartiles
|
||||
/// Q_3 - Q_1 [ or P_75 - P-25 ]
|
||||
fn interquartile(self) -> Number;
|
||||
fn interquartile(self) -> f64;
|
||||
}
|
||||
|
||||
impl Interquartile for Sorted<&Vec<Number>> {
|
||||
fn interquartile(self) -> Number {
|
||||
fn interquartile(self) -> f64 {
|
||||
self.percentile(Number::from(75)) - self.percentile(Number::from(25))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
use crate::sql::number::Number;
|
||||
|
||||
pub trait Mean {
|
||||
fn mean(&self) -> Number;
|
||||
fn mean(&self) -> f64;
|
||||
}
|
||||
|
||||
impl Mean for Vec<Number> {
|
||||
fn mean(&self) -> Number {
|
||||
match self.len() {
|
||||
0 => Number::NAN,
|
||||
_ => {
|
||||
let len = Number::from(self.len());
|
||||
let sum = self.iter().sum::<Number>();
|
||||
sum / len
|
||||
}
|
||||
}
|
||||
fn mean(&self) -> f64 {
|
||||
let len = self.len() as f64;
|
||||
let sum = self.iter().map(|n| n.to_float()).sum::<f64>();
|
||||
|
||||
// Will be NaN if len is 0
|
||||
sum / len
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
use crate::sql::number::{Number, Sorted};
|
||||
|
||||
pub trait Median {
|
||||
fn median(self) -> Number;
|
||||
fn median(self) -> f64;
|
||||
}
|
||||
|
||||
impl Median for Sorted<&Vec<Number>> {
|
||||
fn median(self) -> Number {
|
||||
self.0.get(self.0.len() / 2).unwrap_or(&Number::NAN).clone()
|
||||
fn median(self) -> f64 {
|
||||
if self.0.is_empty() {
|
||||
f64::NAN
|
||||
} else if self.0.len() % 2 == 1 {
|
||||
// return the middle: _ _ X _ _
|
||||
self.0[self.0.len() / 2].to_float()
|
||||
} else {
|
||||
// return the average of the middles: _ _ X Y _ _
|
||||
(self.0[self.0.len() / 2].to_float() + self.0[self.0.len() / 2 + 1].to_float()) / 2.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ use crate::sql::number::{Number, Sorted};
|
|||
|
||||
pub trait Midhinge {
|
||||
/// Tukey Midhinge - the average of the 1st and 3rd Quartiles
|
||||
fn midhinge(&self) -> Number;
|
||||
fn midhinge(&self) -> f64;
|
||||
}
|
||||
|
||||
impl Midhinge for Sorted<&Vec<Number>> {
|
||||
fn midhinge(&self) -> Number {
|
||||
(self.percentile(Number::from(75)) + self.percentile(Number::from(25))) / Number::from(2)
|
||||
fn midhinge(&self) -> f64 {
|
||||
(self.percentile(Number::from(75)) + self.percentile(Number::from(25))) / 2.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ impl Mode for Vec<Number> {
|
|||
fn mode(self) -> Number {
|
||||
// Iterate over all numbers, and get their frequency
|
||||
let frequencies = self.into_iter().fold(BTreeMap::new(), |mut freqs, value| {
|
||||
let entry = freqs.entry(value).or_insert_with(|| 0);
|
||||
let entry = freqs.entry(value).or_insert_with(|| 0u32);
|
||||
*entry += 1;
|
||||
freqs
|
||||
});
|
||||
|
|
|
@ -12,19 +12,11 @@ impl Nearestrank for Sorted<&Vec<Number>> {
|
|||
return Number::NAN;
|
||||
}
|
||||
// If an invalid percentile, then return NaN
|
||||
if (perc <= Number::from(0)) | (perc > Number::from(100)) {
|
||||
let perc = perc.as_float();
|
||||
if !(0.0..=100.0).contains(&perc) {
|
||||
return Number::NAN;
|
||||
}
|
||||
// If 100%, then get the last value in the set
|
||||
if perc == Number::from(100) {
|
||||
return self.0.get(self.0.len()).unwrap_or(&Number::NAN).clone();
|
||||
}
|
||||
// Get the index of the specified percentile
|
||||
let n_percent_idx = Number::from(self.0.len()) * perc / Number::from(100);
|
||||
// Return the closest extant record for the index
|
||||
match n_percent_idx.as_float().ceil() as usize {
|
||||
0 => self.0.get(0).unwrap_or(&Number::NAN).clone(),
|
||||
idx => self.0.get(idx - 1).unwrap_or(&Number::NAN).clone(),
|
||||
}
|
||||
let idx = self.0.len() as f64 * (perc * (1.0 / 100.0));
|
||||
self.0[(idx as usize).min(self.0.len() - 1)].clone()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,33 +2,30 @@ use crate::sql::number::{Number, Sorted};
|
|||
|
||||
pub trait Percentile {
|
||||
/// Gets the N percentile, averaging neighboring records if non-exact
|
||||
fn percentile(&self, perc: Number) -> Number;
|
||||
fn percentile(&self, perc: Number) -> f64;
|
||||
}
|
||||
|
||||
impl Percentile for Sorted<&Vec<Number>> {
|
||||
fn percentile(&self, perc: Number) -> Number {
|
||||
fn percentile(&self, perc: Number) -> f64 {
|
||||
// If an empty set, then return NaN
|
||||
if self.0.is_empty() {
|
||||
return Number::NAN;
|
||||
return f64::NAN;
|
||||
}
|
||||
// If an invalid percentile, then return NaN
|
||||
if (perc <= Number::from(0)) | (perc > Number::from(100)) {
|
||||
return Number::NAN;
|
||||
let perc = perc.to_float();
|
||||
if !(0.0..=100.0).contains(&perc) {
|
||||
return f64::NAN;
|
||||
}
|
||||
// Get the index of the specified percentile
|
||||
let n_percent_idx = Number::from(self.0.len()) * perc / Number::from(100);
|
||||
// Calculate the N percentile for the index
|
||||
if n_percent_idx.to_float().fract().abs() < 1e-10 {
|
||||
let idx = n_percent_idx.as_usize();
|
||||
let val = self.0.get(idx - 1).unwrap_or(&Number::NAN).clone();
|
||||
val
|
||||
} else if n_percent_idx > Number::from(1) {
|
||||
let idx = n_percent_idx.as_usize();
|
||||
let val = self.0.get(idx - 1).unwrap_or(&Number::NAN);
|
||||
let val = val + self.0.get(idx).unwrap_or(&Number::NAN);
|
||||
val / Number::from(2)
|
||||
let fract_index = (self.0.len() - 1) as f64 * perc * (1.0 / 100.0);
|
||||
let floor = self.0[fract_index.floor() as usize].to_float();
|
||||
let fract = fract_index.fract();
|
||||
|
||||
if fract.abs() <= f64::EPSILON {
|
||||
floor
|
||||
} else {
|
||||
Number::NAN
|
||||
let ceil = self.0[fract_index.ceil() as usize].to_float();
|
||||
floor + (ceil - floor) * fract
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ use crate::sql::number::{Number, Sorted};
|
|||
|
||||
pub trait Quartile {
|
||||
/// Divides the set of numbers into Q_0 (min), Q_1, Q_2, Q_3, and Q_4 (max)
|
||||
fn quartile(self) -> (Number, Number, Number, Number, Number);
|
||||
fn quartile(self) -> (f64, f64, f64, f64, f64);
|
||||
}
|
||||
|
||||
impl Quartile for Sorted<&Vec<Number>> {
|
||||
fn quartile(self) -> (Number, Number, Number, Number, Number) {
|
||||
fn quartile(self) -> (f64, f64, f64, f64, f64) {
|
||||
(
|
||||
self.percentile(Number::from(0)),
|
||||
self.percentile(Number::from(25)),
|
||||
|
|
|
@ -4,11 +4,11 @@ use crate::sql::number::{Number, Sorted};
|
|||
pub trait Trimean {
|
||||
/// Bowley's Trimean - the Average of the median and the MidHinge
|
||||
/// ( 2 * Q_2 + Q_1 + Q_3 ) / 4 == ( Q_2 + ( Q_1 + Q_3 ) ) / 2
|
||||
fn trimean(self) -> Number;
|
||||
fn trimean(self) -> f64;
|
||||
}
|
||||
|
||||
impl Trimean for Sorted<&Vec<Number>> {
|
||||
fn trimean(self) -> Number {
|
||||
(self.midhinge() + self.median()) / Number::from(2)
|
||||
fn trimean(self) -> f64 {
|
||||
(self.midhinge() + self.median()) * 0.5
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,18 @@ use crate::sql::number::Number;
|
|||
pub trait Variance {
|
||||
/// Population Variance of Data
|
||||
/// O(n) time complex
|
||||
fn variance(self, sample: bool) -> Number;
|
||||
fn variance(self, sample: bool) -> f64;
|
||||
}
|
||||
|
||||
impl Variance for Vec<Number> {
|
||||
fn variance(self, sample: bool) -> Number {
|
||||
fn variance(self, sample: bool) -> f64 {
|
||||
match self.len() {
|
||||
0 => Number::NAN,
|
||||
1 => Number::from(0),
|
||||
0 => f64::NAN,
|
||||
1 => 0.0,
|
||||
len => {
|
||||
let mean = self.mean();
|
||||
let len = Number::from(len - sample as usize);
|
||||
let out = self.iter().map(|x| (x - &mean) * (x - &mean)).sum::<Number>() / len;
|
||||
let len = (len - sample as usize) as f64;
|
||||
let out = self.iter().map(|x| (x.to_float() - mean).powi(2)).sum::<f64>() / len;
|
||||
out
|
||||
}
|
||||
}
|
||||
|
|
|
@ -412,6 +412,8 @@ impl Ord for Number {
|
|||
}
|
||||
}
|
||||
|
||||
// Warning: Equal numbers may have different hashes, which violates
|
||||
// the invariants of certain collections!
|
||||
impl hash::Hash for Number {
|
||||
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
|
@ -556,6 +558,7 @@ impl ops::Div for Number {
|
|||
type Output = Self;
|
||||
fn div(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Number::Int(v), Number::Int(w)) => Number::Int(v / w),
|
||||
(Number::Float(v), Number::Float(w)) => Number::Float(v / w),
|
||||
(Number::Decimal(v), Number::Decimal(w)) => Number::Decimal(v / w),
|
||||
(Number::Int(v), Number::Float(w)) => Number::Float(v as f64 / w),
|
||||
|
@ -569,6 +572,7 @@ impl<'a, 'b> ops::Div<&'b Number> for &'a Number {
|
|||
type Output = Number;
|
||||
fn div(self, other: &'b Number) -> Number {
|
||||
match (self, other) {
|
||||
(Number::Int(v), Number::Int(w)) => Number::Int(v / w),
|
||||
(Number::Float(v), Number::Float(w)) => Number::Float(v / w),
|
||||
(Number::Decimal(v), Number::Decimal(w)) => Number::Decimal(v / w),
|
||||
(Number::Int(v), Number::Float(w)) => Number::Float(*v as f64 / w),
|
||||
|
@ -657,6 +661,7 @@ fn decimal(i: &str) -> IResult<&str, Number> {
|
|||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use std::ops::Div;
|
||||
|
||||
#[test]
|
||||
fn number_int() {
|
||||
|
@ -758,6 +763,12 @@ mod tests {
|
|||
assert_eq!(out, Number::try_from("13.571938471938471938563985639413947693775636").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_div_int() {
|
||||
let res = Number::Int(3).div(Number::Int(2));
|
||||
assert_eq!(res, Number::Int(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_pow_int() {
|
||||
let res = Number::Int(3).pow(Number::Int(4));
|
||||
|
|
|
@ -2073,11 +2073,11 @@ async fn function_math_interquartile() -> Result<(), Error> {
|
|||
assert!(tmp.is_nan());
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(207.5);
|
||||
let val = Value::from(56.0);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(208.0);
|
||||
let val = Value::from(56.0);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
Ok(())
|
||||
|
@ -2179,11 +2179,11 @@ async fn function_math_midhinge() -> Result<(), Error> {
|
|||
assert!(tmp.is_nan());
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(103.75);
|
||||
let val = Value::from(179.5);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(104.0);
|
||||
let val = Value::from(180);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
Ok(())
|
||||
|
@ -2284,11 +2284,11 @@ async fn function_math_percentile() -> Result<(), Error> {
|
|||
assert!(tmp.is_nan());
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(207.5);
|
||||
let val = Value::from(212.78);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(208.0);
|
||||
let val = Value::from(213.28);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
Ok(())
|
||||
|
@ -2429,11 +2429,11 @@ async fn function_math_stddev() -> Result<(), Error> {
|
|||
assert!(tmp.is_nan());
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse("61.73329733620260786466504830446900810163706056134726969779498735043443723773086343343420617365104296");
|
||||
let val = Value::from(61.73329733620261);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse("61.73329733620260786466504830446900810163706056134726969779498735043443723773086343343420617365104296");
|
||||
let val = Value::from(61.73329733620261);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
Ok(())
|
||||
|
@ -2511,11 +2511,11 @@ async fn function_math_trimean() -> Result<(), Error> {
|
|||
assert!(tmp.is_nan());
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(152.875);
|
||||
let val = Value::from(190.75);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::from(153.25);
|
||||
let val = Value::from(191.25);
|
||||
assert_eq!(tmp, val);
|
||||
//
|
||||
Ok(())
|
||||
|
|
Loading…
Reference in a new issue