Refactor - Use floats for math and cvt'ing from JS numbers, integer division truncates ()

This commit is contained in:
Finn Bear 2023-05-22 23:10:09 -07:00 committed by GitHub
parent 0c033318fc
commit a81d427c02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 88 additions and 74 deletions

View file

@ -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));
}
}

View file

@ -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());

View file

@ -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()
}
}

View file

@ -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))
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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
});

View file

@ -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()
}
}

View file

@ -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
}
}
}

View file

@ -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)),

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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));

View file

@ -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(())