Added '@' and '$' functions(RNG and INT function)
dc / build (push) Successful in 13s Details

This commit is contained in:
Marco Cetica 2024-03-14 15:54:50 +01:00
parent 0e15856762
commit d0156fae3a
Signed by: marco
GPG Key ID: 45060A949E90D0FD
9 changed files with 187 additions and 52 deletions

View File

@ -26,6 +26,8 @@ Some of the supported features are:
- Trigonometrical functions(`sin`, `cos`, `tan`, `asin`, `acos`, `atan`);
- Base conversion(binary: `pb`, octal: `po`, hexadecimal: `px`);
- Factorial and constants(`!`, `pi`, `e`);
- Random number generator(`@`);
- Integer conversion(`$`);
- Stack operations:
- Print top element(`p`, `P`);
- Clear the stack(`c`);
@ -246,6 +248,17 @@ lB -1 * lD + lA # POSITIVE DELTA
[ X2: ] P R LS lS p
```
16. Generate $n$ (pseudo)random numbers from user-defined range:
```
5 k
[ lA lB @ p ] sR
[ Enter number of samples: ] P ? sN
[ Enter lower bound: ] P ? sA
[ Enter upper bound: ] P ? sB
[ lR x r 1 + d lN >=L ] sL
0 lL x
```
## License
[GPLv3](https://choosealicense.com/licenses/gpl-3.0/)

32
man.md
View File

@ -3,7 +3,7 @@ title: dc
section: 1
header: General Commands Manual
footer: Marco Cetica
date: March 13, 2024
date: March 14, 2024
---
@ -102,7 +102,7 @@ the map's `key` and the associated value is represented by the map's `value`.
By default each value of any kind of stack is represented by a string. Each operation is in charge to type convert the value before and after
their invocation. The user can store both numeric and alphanumeric values on the stack. The latter using the _macro_ syntax(see below).
Arrays are homogeneous, thus the only supported data type is the `string`(the internal string type and not the **dc** one).
Arrays are homogeneous data structures that implement the same data type of the stack, i.e. the string.
# COMMANDS
Below, there is a list of supported **dc** commands.
@ -121,7 +121,7 @@ Pops off the value on top of the stack, without altering the stack.
Prints the entire contents of the stack without altering anything.
## Arithmetic
## Mathematics
**+**
@ -167,11 +167,20 @@ Pops one value, computes its factorial, and pushes that.
**pi**
Pushes pi approximation
Pushes pi approximation.
**e**
Pushes e approximation
Pushes e approximation.
**@**
Pops two values from the stack and generate a random number, using the first value popped as the upper bound and the second popped as the lower bound.
The random value is generated using a 64-bit Mersenne Twister pseudorandom number generator and a real uniform distribution.
**$**
Pops one value from the stack and convert it to the nearest integer of lesser magnitute.
## Trigonometrical
@ -474,7 +483,7 @@ lB -1 * lD + lA # POSITIVE DELTA
```
11. Load an external file:
```sh
```
$> echo "[ p 1 + d lN >=L ] sL" > loop.dc
$> cat prg.dc
[ loop.dc ] ' # Load loop macro
@ -483,6 +492,17 @@ $> cat prg.dc
c 1 lL x # Clear the stack, add lower bound, load and execute macro
```
12. Generate *n* (pseudo)random numbers from user-defined range:
```
5 k
[ lA lB @ p ] sR
[ Enter number of samples: ] P ? sN
[ Enter lower bound: ] P ? sA
[ Enter upper bound: ] P ? sB
[ lR x r 1 + d lN >=L ] sL
0 lL x
```
# AUTHORS
The original version of the **dc** command was written by Robert Morris and Lorinda Cherry.
This version of **dc** is developed by Marco Cetica.

View File

@ -3,7 +3,7 @@ project(src)
set(HEADER_FILES
eval.h
macro.h
math.h
mathematics.h
operation.h
stack.h
adt.h
@ -12,9 +12,9 @@ set(HEADER_FILES
set(SOURCE_FILES
eval.cpp
macro.cpp
math.cpp
mathematics.cpp
stack.cpp
adt.cpp
)
add_library(src STATIC ${SOURCE_FILES} ${HEADER_FILES})
add_library(src STATIC ${SOURCE_FILES} ${HEADER_FILES})

View File

@ -2,7 +2,7 @@
#include "adt.cpp"
#include "eval.h"
#include "math.h"
#include "mathematics.h"
#include "stack.h"
#include "macro.h"
#include "is_num.h"
@ -22,24 +22,26 @@
void Evaluate::init_environment() {
// Numerical operations
this->op_factory.emplace("+", MAKE_UNIQUE_PTR(Math, OPType::ADD));
this->op_factory.emplace("-", MAKE_UNIQUE_PTR(Math, OPType::SUB));
this->op_factory.emplace("*", MAKE_UNIQUE_PTR(Math, OPType::MUL));
this->op_factory.emplace("/", MAKE_UNIQUE_PTR(Math, OPType::DIV));
this->op_factory.emplace("%", MAKE_UNIQUE_PTR(Math, OPType::MOD));
this->op_factory.emplace("~", MAKE_UNIQUE_PTR(Math, OPType::DIV_MOD));
this->op_factory.emplace("|", MAKE_UNIQUE_PTR(Math, OPType::MOD_EXP));
this->op_factory.emplace("^", MAKE_UNIQUE_PTR(Math, OPType::EXP));
this->op_factory.emplace("v", MAKE_UNIQUE_PTR(Math, OPType::SQRT));
this->op_factory.emplace("sin", MAKE_UNIQUE_PTR(Math, OPType::SIN));
this->op_factory.emplace("cos", MAKE_UNIQUE_PTR(Math, OPType::COS));
this->op_factory.emplace("tan", MAKE_UNIQUE_PTR(Math, OPType::TAN));
this->op_factory.emplace("asin", MAKE_UNIQUE_PTR(Math, OPType::ASIN));
this->op_factory.emplace("acos", MAKE_UNIQUE_PTR(Math, OPType::ACOS));
this->op_factory.emplace("atan", MAKE_UNIQUE_PTR(Math, OPType::ATAN));
this->op_factory.emplace("!", MAKE_UNIQUE_PTR(Math, OPType::FACT));
this->op_factory.emplace("pi", MAKE_UNIQUE_PTR(Math, OPType::PI));
this->op_factory.emplace("e", MAKE_UNIQUE_PTR(Math, OPType::E));
this->op_factory.emplace("+", MAKE_UNIQUE_PTR(Mathematics, OPType::ADD));
this->op_factory.emplace("-", MAKE_UNIQUE_PTR(Mathematics, OPType::SUB));
this->op_factory.emplace("*", MAKE_UNIQUE_PTR(Mathematics, OPType::MUL));
this->op_factory.emplace("/", MAKE_UNIQUE_PTR(Mathematics, OPType::DIV));
this->op_factory.emplace("%", MAKE_UNIQUE_PTR(Mathematics, OPType::MOD));
this->op_factory.emplace("~", MAKE_UNIQUE_PTR(Mathematics, OPType::DIV_MOD));
this->op_factory.emplace("|", MAKE_UNIQUE_PTR(Mathematics, OPType::MOD_EXP));
this->op_factory.emplace("^", MAKE_UNIQUE_PTR(Mathematics, OPType::EXP));
this->op_factory.emplace("v", MAKE_UNIQUE_PTR(Mathematics, OPType::SQRT));
this->op_factory.emplace("sin", MAKE_UNIQUE_PTR(Mathematics, OPType::SIN));
this->op_factory.emplace("cos", MAKE_UNIQUE_PTR(Mathematics, OPType::COS));
this->op_factory.emplace("tan", MAKE_UNIQUE_PTR(Mathematics, OPType::TAN));
this->op_factory.emplace("asin", MAKE_UNIQUE_PTR(Mathematics, OPType::ASIN));
this->op_factory.emplace("acos", MAKE_UNIQUE_PTR(Mathematics, OPType::ACOS));
this->op_factory.emplace("atan", MAKE_UNIQUE_PTR(Mathematics, OPType::ATAN));
this->op_factory.emplace("!", MAKE_UNIQUE_PTR(Mathematics, OPType::FACT));
this->op_factory.emplace("pi", MAKE_UNIQUE_PTR(Mathematics, OPType::PI));
this->op_factory.emplace("e", MAKE_UNIQUE_PTR(Mathematics, OPType::E));
this->op_factory.emplace("@", MAKE_UNIQUE_PTR(Mathematics, OPType::RND));
this->op_factory.emplace("$", MAKE_UNIQUE_PTR(Mathematics, OPType::INT));
// Stack operations
this->op_factory.emplace("p", MAKE_UNIQUE_PTR(Stack, OPType::PCG));
this->op_factory.emplace("pb", MAKE_UNIQUE_PTR(Stack, OPType::PBB));

View File

@ -1,12 +1,13 @@
#include <cmath>
#include <numbers>
#include <iomanip>
#include <random>
#include "adt.cpp"
#include "math.h"
#include "mathematics.h"
#include "is_num.h"
std::optional<std::string> Math::exec(dc::Stack<std::string> &stack, dc::Parameters &parameters, __attribute__((unused)) std::unordered_map<char, dc::Register> &regs) {
std::optional<std::string> Mathematics::exec(dc::Stack<std::string> &stack, dc::Parameters &parameters, __attribute__((unused)) std::unordered_map<char, dc::Register> &regs) {
std::optional<std::string> err = std::nullopt;
switch(this->op_type) {
@ -28,13 +29,15 @@ std::optional<std::string> Math::exec(dc::Stack<std::string> &stack, dc::Paramet
case OPType::FACT: err = fn_fact(stack, parameters); break;
case OPType::PI: err = fn_pi(stack, parameters); break;
case OPType::E: err = fn_e(stack, parameters); break;
case OPType::RND: err = fn_random(stack, parameters); break;
case OPType::INT: err = fn_integer(stack, parameters); break;
default: break;
}
return err;
}
std::optional<std::string> Math::fn_add(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_add(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'+' requires two operands";
@ -62,7 +65,7 @@ std::optional<std::string> Math::fn_add(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_sub(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_sub(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'-' requires two operands";
@ -100,7 +103,7 @@ std::optional<std::string> Math::fn_sub(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_mul(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_mul(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'*' requires two operands";
@ -128,7 +131,7 @@ std::optional<std::string> Math::fn_mul(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_div(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_div(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'/' requires two operands";
@ -161,7 +164,7 @@ std::optional<std::string> Math::fn_div(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_mod(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_mod(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'%' requires two operands";
@ -194,7 +197,7 @@ std::optional<std::string> Math::fn_mod(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_div_mod(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_div_mod(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'~' requires two operands";
@ -229,7 +232,7 @@ std::optional<std::string> Math::fn_div_mod(dc::Stack<std::string> &stack, const
return std::nullopt;
}
std::optional<std::string> Math::fn_mod_exp(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_mod_exp(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 3) {
return "'|' requires three operands";
@ -278,7 +281,7 @@ std::optional<std::string> Math::fn_mod_exp(dc::Stack<std::string> &stack, const
return std::nullopt;
}
std::optional<std::string> Math::fn_exp(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_exp(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'^' requires two operands";
@ -306,7 +309,7 @@ std::optional<std::string> Math::fn_exp(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_sqrt(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_sqrt(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'v' requires one operand";
@ -335,7 +338,7 @@ std::optional<std::string> Math::fn_sqrt(dc::Stack<std::string> &stack, const dc
return std::nullopt;
}
std::optional<std::string> Math::fn_sin(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_sin(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'sin' requires one operand";
@ -360,7 +363,7 @@ std::optional<std::string> Math::fn_sin(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_cos(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_cos(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'cos' requires one operand";
@ -385,7 +388,7 @@ std::optional<std::string> Math::fn_cos(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_tan(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_tan(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'tan' requires one operand";
@ -410,7 +413,7 @@ std::optional<std::string> Math::fn_tan(dc::Stack<std::string> &stack, const dc:
return std::nullopt;
}
std::optional<std::string> Math::fn_asin(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_asin(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'asin' requires one operand";
@ -435,7 +438,7 @@ std::optional<std::string> Math::fn_asin(dc::Stack<std::string> &stack, const dc
return std::nullopt;
}
std::optional<std::string> Math::fn_acos(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_acos(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'acos' requires one operand";
@ -460,7 +463,7 @@ std::optional<std::string> Math::fn_acos(dc::Stack<std::string> &stack, const dc
return std::nullopt;
}
std::optional<std::string> Math::fn_atan(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_atan(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'atan' requires one operand";
@ -485,7 +488,7 @@ std::optional<std::string> Math::fn_atan(dc::Stack<std::string> &stack, const dc
return std::nullopt;
}
std::optional<std::string> Math::fn_fact(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_fact(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'!' requires one operand";
@ -519,19 +522,75 @@ std::optional<std::string> Math::fn_fact(dc::Stack<std::string> &stack, const dc
return std::nullopt;
}
std::optional<std::string> Math::fn_pi(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_pi(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
stack.push(trim_digits(std::numbers::pi, parameters.precision));
return std::nullopt;
}
std::optional<std::string> Math::fn_e(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
std::optional<std::string> Mathematics::fn_e(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
stack.push(trim_digits(std::numbers::e, parameters.precision));
return std::nullopt;
}
std::string Math::trim_digits(double number, unsigned int precision) {
std::optional<std::string> Mathematics::fn_random(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.size() < 2) {
return "'@' requires two operands";
}
// Extract two entries from the stack
auto len = stack.size() - 1;
auto b = stack[len];
auto a = stack[len-1];
auto is_a_num = is_num<double>(a);
auto is_b_num = is_num<double>(b);
// Check whether both entries are numbers
if(is_a_num && is_b_num) {
stack.copy_xyz();
auto u_bound = std::stod(stack.pop(true));
auto l_bound = std::stod(stack.pop(true));
// Initialize random distribution with user bounds( [a, b] )
std::random_device r_dev;
std::mt19937_64 rng(r_dev());
std::uniform_real_distribution<double> u_dist(l_bound, u_bound);
auto r_number = u_dist(rng);
// Push the random value onto the stack
stack.push(trim_digits(r_number, parameters.precision));
} else {
return "'@' requires numeric values";
}
return std::nullopt;
}
std::optional<std::string> Mathematics::fn_integer(dc::Stack<std::string> &stack, const dc::Parameters &parameters) {
// Check if stack has enough elements
if(stack.empty()) {
return "'$' requires one operand";
}
auto head = stack.pop(false);
auto is_head_num = is_num<double>(head);
// Check whether head of the stack is a number
if(is_head_num) {
stack.copy_xyz();
// Convert to integral type to truncate
auto value = std::stol(stack.pop(true));
// Push the truncated number back to the stack
stack.push(trim_digits(static_cast<double>(value), parameters.precision));
} else {
return "'$' requires numeric values";
}
return std::nullopt;
}
std::string Mathematics::trim_digits(double number, unsigned int precision) {
std::ostringstream oss;
// Preserve non-zero decimal numbers even when precision is zero

View File

@ -2,9 +2,9 @@
#include "operation.h"
class Math : public IOperation {
class Mathematics : public IOperation {
public:
explicit Math(const OPType op_t) : op_type(op_t) {}
explicit Mathematics(const OPType op_t) : op_type(op_t) {}
std::optional<std::string> exec(dc::Stack<std::string> &stack, dc::Parameters &parameters, std::unordered_map<char, dc::Register> &regs) override;
private:
@ -26,6 +26,8 @@ private:
static std::optional<std::string> fn_fact(dc::Stack<std::string> &stack, const dc::Parameters &parameters);
static std::optional<std::string> fn_pi(dc::Stack<std::string> &stack, const dc::Parameters &parameters);
static std::optional<std::string> fn_e(dc::Stack<std::string> &stack, const dc::Parameters &parameters);
static std::optional<std::string> fn_random(dc::Stack<std::string> &stack, const dc::Parameters &parameters);
static std::optional<std::string> fn_integer(dc::Stack<std::string> &stack, const dc::Parameters &parameters);
static std::string trim_digits(double number, unsigned int precision);
OPType op_type;

View File

@ -12,7 +12,8 @@ public:
enum class OPType {
// Numerical operations
ADD, SUB, MUL, DIV, MOD, DIV_MOD, MOD_EXP, EXP,
SQRT, SIN, COS, TAN, ASIN, ACOS, ATAN, FACT, PI, E,
SQRT, SIN, COS, TAN, ASIN, ACOS, ATAN, FACT, PI,
E, RND, INT,
// Stack operations
PCG, P, PBB, PBH, PBO, CLR, PH, SO, DP, PS, CH, CS,
SP, GP, SOR, GOR, SIR, GIR, LX, LY, LZ,

19
tests/test_int Normal file
View File

@ -0,0 +1,19 @@
#!/bin/sh
utest() {
PROGRAM="$PWD/build/dc"
EXPECTED="3"
ACTUAL=$("$PROGRAM" -e 'pi $ p')
assert_eq "$EXPECTED" "$ACTUAL"
# Test empty stack
EXPECTED="'$' requires one operand"
ACTUAL=$("$PROGRAM" -e '$' 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
# Test non numerical values
EXPECTED="'$' requires numeric values"
ACTUAL=$("$PROGRAM" -e '[ foo ] $' 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
}
# vim: ts=4 sw=4 softtabstop=4 expandtab:

19
tests/test_rnd Normal file
View File

@ -0,0 +1,19 @@
#!/bin/sh
utest() {
PROGRAM="$PWD/build/dc"
ACTUAL=$("$PROGRAM" -e '-10 10 @ $ p')
assert_ge "$ACTUAL" "-10"
assert_le "$ACTUAL" "10"
# Test empty stack
EXPECTED="'@' requires two operands"
ACTUAL=$("$PROGRAM" -e '@' 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
# Test non numerical values
EXPECTED="'@' requires numeric values"
ACTUAL=$("$PROGRAM" -e '[ foo ] 5 @' 2>&1) || true
assert_eq "$EXPECTED" "$ACTUAL"
}
# vim: ts=4 sw=4 softtabstop=4 expandtab: