277 lines
9.3 KiB
C++
277 lines
9.3 KiB
C++
#include <cmath>
|
|
#include <iomanip>
|
|
|
|
#include "adt.cpp"
|
|
#include "statistics.h"
|
|
#include "is_num.h"
|
|
|
|
std::optional<std::string> Statistics::exec(dc::Stack<std::string> &stack, dc::Parameters ¶meters, std::unordered_map<char, dc::Register> ®s) {
|
|
std::optional<std::string> err = std::nullopt;
|
|
|
|
switch(this->op_type) {
|
|
case OPType::PERM: err = fn_perm(stack, parameters); break;
|
|
case OPType::COMB: err = fn_comb(stack, parameters); break;
|
|
case OPType::SUMX: err = fn_sum(stack, parameters, regs); break;
|
|
case OPType::SUMXX: err = fn_sum_squared(stack, parameters, regs); break;
|
|
case OPType::MEAN: err = fn_mean(stack, parameters, regs); break;
|
|
case OPType::SDEV: err = fn_sdev(stack, parameters, regs); break;
|
|
case OPType::LREG: err = fn_lreg(stack, parameters, regs); break;
|
|
default: break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_perm(dc::Stack<std::string> &stack, const dc::Parameters ¶meters) {
|
|
// Check if stack has enough elements
|
|
if(stack.size() < 2) {
|
|
return "'gP' requires two operands";
|
|
}
|
|
|
|
// Extract two entries from the stack
|
|
auto len = stack.size()-1;
|
|
auto head = stack[len];
|
|
auto second = stack[len-1];
|
|
auto is_head_num = is_num<long long>(head);
|
|
auto is_second_num = is_num<long long>(second);
|
|
|
|
// Check whether both entries are integers
|
|
if(is_head_num && is_second_num) {
|
|
stack.copy_xyz();
|
|
auto x = std::stoll(stack.pop(true));
|
|
auto y = std::stoll(stack.pop(true));
|
|
|
|
// Compute factorial of numerator and denominator
|
|
// and check if result is a non-negative integer
|
|
auto numerator_opt = factorial(y);
|
|
auto denominator_opt = factorial(y - x);
|
|
if(numerator_opt == std::nullopt || denominator_opt == std::nullopt) {
|
|
return "'gP' requires positive integers";
|
|
}
|
|
|
|
unsigned long long permutation = numerator_opt.value() / denominator_opt.value();
|
|
stack.push(trim_digits(static_cast<double>(permutation), parameters.precision));
|
|
} else {
|
|
return "'gP' requires integer values";
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_comb(dc::Stack<std::string> &stack, const dc::Parameters ¶meters) {
|
|
// Check if stack has enough elements
|
|
if(stack.size() < 2) {
|
|
return "'gC' requires two operands";
|
|
}
|
|
|
|
// Extract two entries from the stack
|
|
auto len = stack.size()-1;
|
|
auto head = stack[len];
|
|
auto second = stack[len-1];
|
|
auto is_head_num = is_num<long long>(head);
|
|
auto is_second_num = is_num<long long>(second);
|
|
|
|
// Check whether both entries are integers
|
|
if(is_head_num && is_second_num) {
|
|
stack.copy_xyz();
|
|
auto n = std::stoull(stack.pop(true));
|
|
auto k = std::stoull(stack.pop(true));
|
|
|
|
// Check if combination is non-negative
|
|
if(n > k) {
|
|
return "'gC' requires positive integers";
|
|
}
|
|
|
|
// From Knuth's "Seminumerical Algorithms" book
|
|
unsigned long long combination = 1;
|
|
for(unsigned long long i = 1; i <= n; i++) {
|
|
combination *= k--;
|
|
combination /= i;
|
|
}
|
|
|
|
stack.push(trim_digits(static_cast<double>(combination), parameters.precision));
|
|
} else {
|
|
return "'gC' requires integer values";
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_sum(dc::Stack<std::string> &stack, const dc::Parameters ¶meters, std::unordered_map<char, dc::Register> ®s) {
|
|
// Check whether 'x' register exists
|
|
if(regs.find('X') == regs.end()) {
|
|
return "Register 'X' is undefined";
|
|
}
|
|
|
|
// Check if register's stack is empty
|
|
if(regs['X'].stack.empty()) {
|
|
return "The stack of register 'X' is empty";
|
|
}
|
|
|
|
// Othewise retrieve summation of register's stack
|
|
auto summation = regs['X'].stack.summation();
|
|
stack.push(trim_digits(summation, parameters.precision));
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_sum_squared(dc::Stack<std::string> &stack, const dc::Parameters ¶meters, std::unordered_map<char, dc::Register> ®s) {
|
|
// Check whether 'x' register exists
|
|
if(regs.find('X') == regs.end()) {
|
|
return "Register 'X' is undefined";
|
|
}
|
|
|
|
// Check if register's stack is empty
|
|
if(regs['X'].stack.empty()) {
|
|
return "The stack of register 'X' is empty";
|
|
}
|
|
|
|
// Othewise retrieve summation of squares of register's stack
|
|
auto summation_squared = regs['X'].stack.summation_squared();
|
|
stack.push(trim_digits(summation_squared, parameters.precision));
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_mean(dc::Stack<std::string> &stack, const dc::Parameters ¶meters, std::unordered_map<char, dc::Register> ®s) {
|
|
// Check whether 'x' register exists
|
|
if(regs.find('X') == regs.end()) {
|
|
return "Register 'X' is undefined";
|
|
}
|
|
|
|
// Check if register's stack is empty
|
|
if(regs['X'].stack.empty()) {
|
|
return "The stack of register 'X' is empty";
|
|
}
|
|
|
|
// Otherwise compute mean
|
|
auto summation = regs['X'].stack.summation();
|
|
auto size = regs['X'].stack.size();
|
|
auto mean = summation / static_cast<double>(size);
|
|
stack.push(trim_digits(mean, parameters.precision));
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_sdev(dc::Stack<std::string> &stack, const dc::Parameters ¶meters, std::unordered_map<char, dc::Register> ®s) {
|
|
// Check whether 'x' register exists
|
|
if(regs.find('X') == regs.end()) {
|
|
return "Register 'X' is undefined";
|
|
}
|
|
|
|
// Check if register's stack is empty
|
|
if(regs['X'].stack.empty()) {
|
|
return "The stack of register 'X' is empty";
|
|
}
|
|
|
|
// Otherwise, compute the mean
|
|
auto summation = regs['X'].stack.summation();
|
|
auto count = regs['X'].stack.size();
|
|
auto mean = summation / static_cast<double>(count);
|
|
|
|
// Then compute the sum of the deviations from the mean and square the result
|
|
const auto& const_vec = regs['X'].stack.get_ref();
|
|
double sum_of_deviations = std::accumulate(const_vec.begin(), const_vec.end(), 0.0,
|
|
[&](double acc, const std::string& val) {
|
|
double deviation = std::stod(val) - mean;
|
|
return acc + std::pow(deviation, 2);
|
|
});
|
|
// Then compute the mean of previous values(variance)
|
|
auto variance = sum_of_deviations / (static_cast<double>(count) - 1);
|
|
|
|
// Finally, compute the square root of the variance(standard deviation)
|
|
auto s_dev = sqrt(variance);
|
|
|
|
stack.push(trim_digits(s_dev, parameters.precision));
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> Statistics::fn_lreg(dc::Stack<std::string> &stack, const dc::Parameters ¶meters, std::unordered_map<char, dc::Register> ®s) {
|
|
// Check whether 'x' register exists
|
|
if(regs.find('X') == regs.end()) {
|
|
return "Register 'X' is undefined";
|
|
}
|
|
|
|
// Check if register's stack is empty
|
|
if(regs['X'].stack.empty()) {
|
|
return "The stack of register 'X' is empty";
|
|
}
|
|
|
|
// Check whether 'y' register exists
|
|
if(regs.find('Y') == regs.end()) {
|
|
return "Register 'Y' is undefined";
|
|
}
|
|
|
|
// Check if register's stack is empty
|
|
if(regs['Y'].stack.empty()) {
|
|
return "The stack of register 'Y' is empty";
|
|
}
|
|
|
|
// Check that both registers have the same length
|
|
if(regs['X'].stack.size() != regs['Y'].stack.size()) {
|
|
return "'X' and 'Y' registers must be of the same length";
|
|
}
|
|
|
|
// Otherwise, retrieve count and summations of both sets
|
|
auto count = regs['X'].stack.size();
|
|
auto x_sum = regs['X'].stack.summation();
|
|
auto y_sum = regs['Y'].stack.summation();
|
|
|
|
// Then compute the sum of products
|
|
const auto& x_ref = regs['X'].stack.get_ref();
|
|
const auto& y_ref = regs['Y'].stack.get_ref();
|
|
std::size_t idx = 0;
|
|
double sum_of_products = 0.0;
|
|
|
|
for(const auto& it : x_ref) {
|
|
auto x = std::stod(it);
|
|
auto y = std::stod(y_ref[idx++]);
|
|
sum_of_products += (x * y);
|
|
}
|
|
|
|
// Then compute the sum of squares
|
|
auto x_sum_squares = regs['X'].stack.summation_squared();
|
|
|
|
// Then compute the slope of the line(m)
|
|
auto slope_numerator = ((static_cast<double>(count) * sum_of_products) - (x_sum * y_sum));
|
|
auto slope_denominator = ((static_cast<double>(count) * x_sum_squares) - std::pow(x_sum, 2));
|
|
auto slope = slope_numerator / slope_denominator;
|
|
|
|
// Then compute the intercept of the line(b)
|
|
auto intercept = (y_sum - (slope * x_sum)) / static_cast<double>(count);
|
|
|
|
// Finally push the slope and the intercept(in this order) into the main stack
|
|
stack.push(trim_digits(slope, parameters.precision));
|
|
stack.push(trim_digits(intercept, parameters.precision));
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<unsigned long long> Statistics::factorial(const long long n) {
|
|
if(n < 0) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
unsigned long long factorial = 1;
|
|
|
|
for(long long i = 2; i <= n; i++) {
|
|
factorial *= i;
|
|
}
|
|
|
|
return factorial;
|
|
}
|
|
|
|
std::string Statistics::trim_digits(double number, unsigned int precision) {
|
|
std::ostringstream oss;
|
|
|
|
// Preserve non-zero decimal numbers even when precision is zero
|
|
if(precision == 0 && std::fmod(number, 1.0) != 0.0) {
|
|
precision = 2;
|
|
}
|
|
|
|
oss << std::fixed << std::setprecision(static_cast<int>(precision)) << number;
|
|
std::string s = oss.str();
|
|
|
|
return s;
|
|
}
|