diff --git a/Cargo.lock b/Cargo.lock index 3754019..897f34d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,6 +156,31 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "csbindgen" version = "1.9.3" @@ -166,6 +191,12 @@ dependencies = [ "syn", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "equivalent" version = "1.0.1" @@ -193,6 +224,7 @@ name = "finlib" version = "0.0.1" dependencies = [ "pyo3", + "rayon", ] [[package]] @@ -426,6 +458,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.11.1" diff --git a/Cargo.toml b/Cargo.toml index 9666c80..851154d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,9 @@ version = "0.0.1" authors = ["sarsoo <andy@sarsoo.xyz>"] edition = "2021" +[workspace.dependencies] +rayon = "1.10.0" + [workspace.dependencies.pyo3] version = "0.23.4" # "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8 diff --git a/FinLib.NET/FinLib.Test/Basic.cs b/FinLib.NET/FinLib.Test/Basic.cs index d23738c..b1e46a7 100644 --- a/FinLib.NET/FinLib.Test/Basic.cs +++ b/FinLib.NET/FinLib.Test/Basic.cs @@ -9,8 +9,26 @@ public class Tests } [Test] - public void TestAdd() + public void TestCompoundInterest() { - FinLib.Add(10, 10).Should().Be(20); + FinLib.CompoundInterest(100, 0.05, 1, 1).Should().Be(105); + } + + [Test] + public void TestCompoundInterestMonthly() + { + Math.Round(FinLib.CompoundInterest(100, 0.05, 1, 12), 2).Should().Be(105.12); + } + + [Test] + public void Covariance() + { + FinLib.Covariance([1d, 2d, 3d, 4], [1d, 2, 3, 4]).Should().Be(1.6666666666666667); + } + + [Test] + public void CovarianceBreaking() + { + FinLib.Covariance([1d, 2d, 3d, 4], [1d]).Should().BeNull(); } } \ No newline at end of file diff --git a/FinLib.NET/FinLib.Test/FinLib.Test.csproj b/FinLib.NET/FinLib.Test/FinLib.Test.csproj index 710a575..f12f863 100644 --- a/FinLib.NET/FinLib.Test/FinLib.Test.csproj +++ b/FinLib.NET/FinLib.Test/FinLib.Test.csproj @@ -10,7 +10,7 @@ <ItemGroup> <PackageReference Include="coverlet.collector" Version="6.0.2"/> - <PackageReference Include="FluentAssertions" Version="8.0.1" /> + <PackageReference Include="FluentAssertions" Version="7.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/> <PackageReference Include="NUnit" Version="4.2.2"/> <PackageReference Include="NUnit.Analyzers" Version="4.3.0"/> diff --git a/FinLib.NET/FinLib/FinLib.cs b/FinLib.NET/FinLib/FinLib.cs index de1a631..00d5183 100644 --- a/FinLib.NET/FinLib/FinLib.cs +++ b/FinLib.NET/FinLib/FinLib.cs @@ -1,9 +1,35 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + namespace FinLib; public static class FinLib { - public static ulong Add(ulong a, ulong b) + public static double CompoundInterest(double principal, double rate, double time, double n) { - return NativeMethods.add(a, b); + return NativeMethods.interest_compound(principal, rate, time, n); + } + + public static double? Covariance(IEnumerable<double> valuesOne, IEnumerable<double> valuesTwo) + { + unsafe { + var valuesOneArr = valuesOne.ToArray(); + var valuesTwoArr = valuesTwo.ToArray(); + fixed (double* ptrOne = valuesOneArr) + fixed (double* ptrTwo = valuesTwoArr) { + var ret = NativeMethods.covariance(ptrOne, (UIntPtr)valuesOneArr.Length, ptrTwo, (UIntPtr) valuesTwoArr.Length); + + if (ret == null) + { + return null; + } + else + { + return *ret; + } + } + } } } \ No newline at end of file diff --git a/FinLib.NET/FinLib/NativeMethods.g.cs b/FinLib.NET/FinLib/NativeMethods.g.cs index 6a5d1a4..8544bd6 100644 --- a/FinLib.NET/FinLib/NativeMethods.g.cs +++ b/FinLib.NET/FinLib/NativeMethods.g.cs @@ -16,11 +16,11 @@ namespace FinLib - [DllImport(__DllName, EntryPoint = "add", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - internal static extern ulong add(ulong left, ulong right); - [DllImport(__DllName, EntryPoint = "interest_compound", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - internal static extern float interest_compound(float principal, float rate, float time, float n); + internal static extern double interest_compound(double principal, double rate, double time, double n); + + [DllImport(__DllName, EntryPoint = "covariance", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern double* covariance(double* arr, nuint len, double* arr_two, nuint len_two); } diff --git a/finlib-cpp/finlib-native.h b/finlib-cpp/finlib-native.h index 3f3c94b..efcf02a 100644 --- a/finlib-cpp/finlib-native.h +++ b/finlib-cpp/finlib-native.h @@ -17,9 +17,9 @@ namespace finlib { extern "C" { -uint64_t add(uint64_t left, uint64_t right); +const double *covariance(const double *arr, size_t len, const double *arr_two, size_t len_two); -float interest_compound(float principal, float rate, float time, float n); +double interest_compound(double principal, double rate, double time, double n); } // extern "C" diff --git a/finlib-ffi/src/lib.rs b/finlib-ffi/src/lib.rs index 3b11c1e..5c79ccb 100644 --- a/finlib-ffi/src/lib.rs +++ b/finlib-ffi/src/lib.rs @@ -1,9 +1,26 @@ +use std::ptr::null; +use std::slice; + #[no_mangle] -pub extern "C" fn add(left: u64, right: u64) -> u64 { - finlib::add(left, right) +pub extern "C" fn interest_compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 { + finlib::interest::compound(principal, rate, time, n) } #[no_mangle] -pub extern "C" fn interest_compound(principal: f32, rate: f32, time: f32, n: f32) -> f32 { - finlib::interest::compound(principal, rate, time, n) +pub unsafe extern "C" fn covariance(arr: *const f64, len: usize, arr_two: *const f64, len_two: usize) -> *const f64 { + let input_array = unsafe { + assert!(!arr.is_null()); + slice::from_raw_parts(arr, len) + }; + + let input_array_two = unsafe { + assert!(!arr_two.is_null()); + slice::from_raw_parts(arr_two, len_two) + }; + + match finlib::stats::covariance(input_array, input_array_two) { + None => null(), + Some(v) => Box::into_raw(Box::new(v)) + } + } \ No newline at end of file diff --git a/finlib-wasm/src/lib.rs b/finlib-wasm/src/lib.rs index 985dde6..1445120 100644 --- a/finlib-wasm/src/lib.rs +++ b/finlib-wasm/src/lib.rs @@ -1,11 +1,11 @@ use wasm_bindgen::prelude::wasm_bindgen; #[wasm_bindgen] -pub fn add(left: u64, right: u64) -> u64 { - finlib::add(left, right) +pub fn compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 { + finlib::interest::compound(principal, rate, time, n) } #[wasm_bindgen] -pub fn compound(principal: f32, rate: f32, time: f32, n: f32) -> f32 { - finlib::interest::compound(principal, rate, time, n) +pub fn covariance(slice: Vec<f64>, slice_two: Vec<f64>) -> Option<f64> { + finlib::stats::covariance(&slice, &slice_two) } \ No newline at end of file diff --git a/finlib/Cargo.toml b/finlib/Cargo.toml index 9fa3e1b..f1d735c 100644 --- a/finlib/Cargo.toml +++ b/finlib/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] pyo3 = { workspace = true, optional = true } +rayon = { workspace = true } [features] py = ["dep:pyo3"] \ No newline at end of file diff --git a/finlib/src/interest/mod.rs b/finlib/src/interest/mod.rs index 9aa3d87..56151fc 100644 --- a/finlib/src/interest/mod.rs +++ b/finlib/src/interest/mod.rs @@ -1,7 +1,11 @@ -pub fn compound(principal: f32, rate: f32, time: f32, n: f32) -> f32 { +pub fn compound_32(principal: f32, rate: f32, time: f32, n: f32) -> f32 { principal * f32::powf( 1f32 + (rate / n), time * n) } + +pub fn compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 { + principal * f64::powf( 1f64 + (rate / n), time * n) +} /// https://www.thecalculatorsite.com/finance/calculators/compoundinterestcalculator.php #[cfg(test)] @@ -9,14 +13,26 @@ mod tests { use super::*; #[test] - fn annual_compound() { - let result = compound(100f32, 0.05f32, 1f32, 1f32); + fn annual_compound_32() { + let result = compound_32(100f32, 0.05f32, 1f32, 1f32); assert_eq!(f32::round(result), 105f32); } #[test] - fn monthly_compound() { - let result = compound(100f32, 0.05f32, 1f32, 12f32); + fn monthly_compound_32() { + let result = compound_32(100f32, 0.05f32, 1f32, 12f32); assert_eq!(f32::round(result * 100f32) / 100f32, 105.12f32); } + + #[test] + fn annual_compound() { + let result = compound(100f64, 0.05f64, 1f64, 1f64); + assert_eq!(f64::round(result), 105f64); + } + + #[test] + fn monthly_compound() { + let result = compound(100f64, 0.05f64, 1f64, 12f64); + assert_eq!(f64::round(result * 100f64) / 100f64, 105.12f64); + } } diff --git a/finlib/src/lib.rs b/finlib/src/lib.rs index fb4057d..40ee7ce 100644 --- a/finlib/src/lib.rs +++ b/finlib/src/lib.rs @@ -1,16 +1,2 @@ pub mod interest; - -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod stats; \ No newline at end of file diff --git a/finlib/src/stats/covariance.rs b/finlib/src/stats/covariance.rs new file mode 100644 index 0000000..70a3735 --- /dev/null +++ b/finlib/src/stats/covariance.rs @@ -0,0 +1,35 @@ +use rayon::prelude::*; +use super::mean; + +pub fn covariance(slice: &[f64], slice_two: &[f64]) -> Option<f64> +{ + match slice.len() - slice_two.len() { + 0 => { + let mean_1 = mean(slice); + let mean_2 = mean(slice_two); + + Some(slice.par_iter().zip(slice_two.par_iter()) + .map(|(x, y)| (x - mean_1) * (y - mean_2)) + .sum::<f64>() + / ((slice.len() - 1) as f64)) + } + _ => None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn covariance_test() { + let result = covariance(&[1f64, 2f64, 3f64, 4f64], &[1f64, 2f64, 3f64, 4f64]); + assert_eq!(result.unwrap(), 1.6666666666666667f64); + } + + #[test] + fn covariance_test_break() { + let result = covariance(&[1f64, 2f64, 3f64, 4f64], &[1f64, 2f64]); + assert_eq!(result.is_none(), true); + } +} \ No newline at end of file diff --git a/finlib/src/stats/mod.rs b/finlib/src/stats/mod.rs new file mode 100644 index 0000000..090f32e --- /dev/null +++ b/finlib/src/stats/mod.rs @@ -0,0 +1,37 @@ +mod covariance; +pub use covariance::*; + +use rayon::prelude::*; + +pub fn mean(slice: &[f64]) -> f64 +{ + slice.par_iter().sum::<f64>() / slice.len() as f64 +} + +pub fn population_variance(slice: &[f64]) -> f64 +{ + let mean = mean(slice); + slice.par_iter() + .map(|x| f64::powi(x - mean, 2)) + .sum::<f64>() + / slice.len() as f64 +} + +pub fn sample_variance(slice: &[f64]) -> f64 +{ + let mean = mean(slice); + slice.par_iter() + .map(|x| f64::powi(x - mean, 2)) + .sum::<f64>() + / ((slice.len() - 1) as f64) +} + +pub fn population_std_dev(slice: &[f64]) -> f64 +{ + f64::sqrt(population_variance(slice)) +} + +pub fn sample_std_dev(slice: &[f64]) -> f64 +{ + f64::sqrt(sample_variance(slice)) +} \ No newline at end of file diff --git a/pyfinlib/src/lib.rs b/pyfinlib/src/lib.rs index 883c922..e0c923b 100644 --- a/pyfinlib/src/lib.rs +++ b/pyfinlib/src/lib.rs @@ -1,18 +1,18 @@ use pyo3::prelude::*; #[pyfunction] -fn add(left: u64, right: u64) -> PyResult<u64> { - Ok(finlib::add(left, right)) +pub fn compound(principal: f64, rate: f64, time: f64, n: f64) -> PyResult<f64> { + Ok(finlib::interest::compound(principal, rate, time, n)) } #[pyfunction] -pub fn compound(principal: f32, rate: f32, time: f32, n: f32) -> PyResult<f32> { - Ok(finlib::interest::compound(principal, rate, time, n)) +pub fn covariance(slice: Vec<f64>, slice_two: Vec<f64>) -> PyResult<Option<f64>> { + Ok(finlib::stats::covariance(&slice, &slice_two)) } #[pymodule] fn pyfinlib(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(add, m)?)?; m.add_function(wrap_pyfunction!(compound, m)?)?; + m.add_function(wrap_pyfunction!(covariance, m)?)?; Ok(()) } \ No newline at end of file