commit 364689aa69f11fd058b1f9295b36996d9d99e284
parent 85178007fff28f8df769ae5cd0f509dc160fe95b
Author: Zack Newman <zack@philomathiclife.com>
Date: Mon, 1 May 2023 17:03:21 -0600
add sqrt. add mod.
Diffstat:
M | Cargo.toml | | | 5 | ++++- |
M | README.md | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
A | build.rs | | | 12 | ++++++++++++ |
M | lang.tex | | | 41 | ++++++++++++++++++++++++----------------- |
M | src/cache.rs | | | 4 | ++-- |
M | src/lib.rs | | | 455 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
M | src/main.rs | | | 45 | +++++++++++++++++++++++++++++++-------------- |
7 files changed, 460 insertions(+), 190 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
name = "calc_rational"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/calc_rational/"
-version = "0.3.0"
+version = "0.4.0"
[lib]
name = "calc_lib"
@@ -26,6 +26,9 @@ num-rational = { version = "0.4.1", default-features = false, features = ["num-b
num-traits = { version = "0.2.15", default-features = false }
rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true }
+[build-dependencies]
+rustc_version = "0.4.0"
+
[features]
rand = ["dep:rand", "std"]
std = []
diff --git a/README.md b/README.md
@@ -42,6 +42,18 @@ rand()
> 939435294927814822
rand(1+9,10!)
> 2660936
+1+4 mod 2 + 1
+> 2
+-5 mod 2
+> 1
+-5 mod -2
+> 1
+5 mod -2
+> 1
+9^0.5
+> 3
+(4/9)^(-1/2)
+> 3/2
q
[zack@laptop ~]$
```
@@ -53,7 +65,7 @@ The following are the list of expressions in descending order of precedence:
2. `!`
3. `^`
4. `-` (unary negation operator)
- 5. `*`, `/`
+ 5. `*`, `/`, `mod`
6. `+`, `-`
All binary operators are left-associative sans `^` which is right-associative.
@@ -70,9 +82,9 @@ then an error will be displayed. Spaces and tabs are *not* ignored; so `1 !` is
will result in an error message.
`^` is the exponentiation operator. The expression left of the operator can evaluate to any rational number;
-however the expression right of the operator must evaluate to an integer unless the expression on the left
-evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate to a
-non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
+however the expression right of the operator must evaluate to an integer or (+/-) 1/2 unless the expression on
+the left evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate
+to a non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
any rational number. Note that `0^0` is defined to be 1.
The unary operator `-` represents negation.
@@ -80,6 +92,9 @@ The unary operator `-` represents negation.
The operators `*` and `/` represent multiplication and division respectively. Expressions right of `/`
must evaluate to any non-zero rational number; otherwise an error will be displayed.
+The binary operator `mod` represents modulo such that *n mod m = r = n - m\*q* for *n,q ∈ ℤ, m ∈ ℤ\\{0}, and r ∈ ℕ*
+where *r* is the minimum non-negative solution.
+
The binary operators `+` and `-` represent addition and subtraction respectively.
With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored.
@@ -98,20 +113,20 @@ An error will be displayed if called incorrectly. `rand()` generates a random 64
A number literal is a non-empty sequence of digits or a non-empty sequence of digits immediately followed by `.`
which is immediately followed by a non-empty sequence of digits (e.g., `134.901`). This means that number
-literals represent precisely all possible ratios of non-negative integers to non-negative integers who sole
-prime factors are 2 or 5. To represent all other rational numbers, the unary operator `-` and binary
-operator `/` must be used.
+literals represent precisely all rational numbers that are equivalent to a ratio of a non-negative integer to
+a positive integer whose sole prime factors are 2 or 5. To represent all other rational numbers, the unary
+operator `-` and binary operator `/` must be used.
## Empty expression
The empty expression (i.e., expression that at most only consists of spaces and tabs) will return
-the result from the previous non-empty and non-store expression in *decimal* form using the minimum number of digits.
+the result from the previous non-(empty/store) expression in *decimal* form using the minimum number of digits.
In the event an infinite number of digits is required, it will be rounded to 9 fractional digits using normal rounding
rules first.
## Store expression
-To store the result of the previous non-empty and non-store expression, one simply passes `s`. In addition to storing the
+To store the result of the previous non-(empty/store) expression, one simply passes `s`. In addition to storing the
result which will subsequently be available via `@`, it displays the result. At most 8 results can be stored at once;
at which point, results that are stored overwrite the oldest result.
@@ -126,8 +141,9 @@ As emphasized, it does not work on expressions; so both `@@` and `@(1)` are gram
## Character encoding
All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`,
-`*`, `/`, `^`, `!`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>, <line feed>, <carriage return>,
-and `q`. Any other byte sequences are grammatically incorrect and will lead to an error message.
+`*`, `/`, `^`, `!`, `mod`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>,
+<line feed>, <carriage return>, and `q`. Any other byte sequences are grammatically incorrect and will
+lead to an error message.
## Errors
@@ -158,7 +174,7 @@ target. Note one must be aware of the ASCII encoding requirement. In particular
```bash
[zack@laptop ~]$ cargo install --all-features calc_rational
Updating crates.io index
- Installing calc_rational v0.3.0
+ Installing calc_rational v0.4.0
Compiling autocfg v1.1.0
Compiling libc v0.2.142
Compiling cfg-if v1.0.0
@@ -171,10 +187,10 @@ target. Note one must be aware of the ASCII encoding requirement. In particular
Compiling rand_core v0.6.4
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
- Compiling calc_rational v0.3.0 (/home/zack/projects/calc_rational)
+ Compiling calc_rational v0.4.0 (/home/zack/calc_rational)
Finished release [optimized] target(s) in 5.54s
Installing /home/zack/.cargo/bin/calc
- Installed package `calc_rational v0.3.0` (executable `calc`)
+ Installed package `calc_rational v0.4.0` (executable `calc`)
```
### Building and testing
@@ -197,56 +213,58 @@ Cloning into 'calc_rational'...
Compiling rand_core v0.6.4
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
- Compiling calc_rational v0.3.0 (/home/zack/calc_rational)
+ Compiling calc_rational v0.4.0 (/home/zack/calc_rational)
Finished release [optimized] target(s) in 8.25s
-[zack@laptop calc_rational]$ cargo t --release --all-features
+ [zack@laptop calc_rational]$ cargo t --all-features
Compiling autocfg v1.1.0
Compiling libc v0.2.142
+ Compiling semver v1.0.17
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.17
Compiling num-traits v0.2.15
Compiling num-integer v0.1.45
Compiling num-bigint v0.4.3
Compiling num-rational v0.4.1
+ Compiling rustc_version v0.4.0
+ Compiling calc_rational v0.4.0 (/home/zack/calc_rational)
Compiling getrandom v0.2.9
Compiling rand_core v0.6.4
Compiling rand_chacha v0.3.1
Compiling rand v0.8.5
- Compiling calc_rational v0.3.0 (/home/zack/projects/calc_rational)
- Finished release [optimized] target(s) in 10.35s
- Running unittests src/lib.rs (target/release/deps/calc_lib-fa299178069cd874)
+ Finished test [unoptimized + debuginfo] target(s) in 2.80s
+ Running unittests src/lib.rs (target/debug/deps/calc_lib-71003fccf12c1022)
running 26 tests
-test cache::tests::test_len ... ok
-test tests::abs ... ok
-test cache::tests::test_get_unsafe ... ok
-test cache::tests::test_get ... ok
test cache::tests::test_push ... ok
+test cache::tests::test_get ... ok
+test cache::tests::test_is_empty ... ok
+test cache::tests::test_get_unsafe ... ok
+test cache::tests::test_len ... ok
test cache::tests::test_index ... ok
+test tests::abs ... ok
test cache::tests::test_new ... ok
-test cache::tests::test_is_empty ... ok
test tests::exit ... ok
test cache::tests::test_index_panic - should panic ... ok
-test tests::mult ... ok
+test tests::factorial ... ok
+test tests::empty ... ok
+test tests::rand_uni ... ignored
test tests::add ... ok
+test tests::store ... ok
test tests::neg ... ok
+test tests::par ... ok
test tests::eval_iter ... ok
-test tests::number_literal ... ok
-test tests::rand ... ok
-test tests::exp ... ok
-test tests::rand_uni ... ignored
+test tests::recall_expression ... ok
test tests::eval ... ok
-test tests::factorial ... ok
-test tests::empty ... ok
-test tests::store ... ok
test tests::round ... ok
-test tests::recall_expression ... ok
-test tests::par ... ok
+test tests::number_literal ... ok
test tests::term ... ok
+test tests::exp ... ok
+test tests::mult ... ok
+test tests::rand ... ok
test result: ok. 25 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
- Running unittests src/main.rs (target/release/deps/calc-c20eab17c4183fda)
+ Running unittests src/main.rs (target/debug/deps/calc-1d58b73f30400d2f)
running 0 tests
diff --git a/build.rs b/build.rs
@@ -0,0 +1,12 @@
+use rustc_version::{version_meta, Channel};
+
+fn main() {
+ // Set cfg flags depending on release channel
+ let channel = match version_meta().unwrap().channel {
+ Channel::Stable => "CHANNEL_STABLE",
+ Channel::Beta => "CHANNEL_BETA",
+ Channel::Nightly => "CHANNEL_NIGHTLY",
+ Channel::Dev => "CHANNEL_DEV",
+ };
+ println!("cargo:rustc-cfg={}", channel)
+}
diff --git a/lang.tex b/lang.tex
@@ -6,13 +6,13 @@ We will define the language, \(L\), of our rational number calculator program.
Define the set of non-terminal symbols to be
\begin{align*}
-N = \{&prog, expr, empty, quit, eval, store, add, mult, neg, exp, fact,\\
-&term, rnd, r, abs, recall, par, dec, int, ws, digit, prev, l\}.
+N = \{&prog, expr, empty, quit, eval, store, add, mult, neg, exp,\\
+&fact,term, rnd, r, abs, recall, par, dec, int, ws, digit, prev, l\}.
\end{align*}
Define the set of terminal symbols to be
\begin{align*}
-\Sigma = \{&0,1,2,3,4,5,6,7,8,9,.,+,-,*,/,\string^,!,|(,),round,,,@,s,space\\
-&rand, tab,lf,cr,q\}.
+\Sigma = \{&0,1,2,3,4,5,6,7,8,9,.,+,-,*,/,\string^,!,mod,|(,),round,,,@,s,\\
+&space,rand, tab,lf,cr,q\}.
\end{align*}
Define the production rules, \(P\), as the following:
\begin{enumerate}
@@ -20,10 +20,10 @@ Define the production rules, \(P\), as the following:
\item \(expr \rightarrow empty\ |\ quit\ |\ eval\ |\ store\)
\item \(empty \rightarrow ws\)
\item \(quit \rightarrow ws\ q\ ws\)
- \item \(eval\rightarrow ws\ add\ ws\)
+ \item \(eval\rightarrow ws\ modulo\ ws\)
\item \(store\rightarrow ws\ s\ ws\)
\item \(add \rightarrow add\ ws\ +\ ws\ mult\ |\ add\ ws\ -\ ws\ mult\ |\ mult\)
- \item \(mult \rightarrow mult\ ws\ *\ ws\ neg\ |\ mult\ ws\ /\ ws\ neg\ |\ neg\)
+ \item \(mult \rightarrow mult\ ws\ *\ ws\ neg\ |\ mult\ ws\ /\ ws\ neg\ |\ mult\ ws\ mod\ ws\ neg\ |\ neg\)
\item \(neg \rightarrow -\ ws\ neg\ |\ exp\)
\item \(exp \rightarrow fact\ ws\ \string^\ ws\ neg\ |\ fact\)
\item \(fact \rightarrow fact!\ |\ term\)
@@ -32,7 +32,7 @@ Define the production rules, \(P\), as the following:
\item \(r \rightarrow round(ws\ add\ ws,ws\ digit\ ws)\)
\item \(abs \rightarrow |ws\ add\ ws|\)
\item \(recall \rightarrow @\ |\ @prev\)
- \item \(par \rightarrow (ws\ add\ ws)\)
+ \item \(par \rightarrow (ws\ add \ ws)\)
\item \(dec \rightarrow int\ |\ int.int\)
\item \(int \rightarrow digit\ |\ digit\ int\)
\item \(ws \rightarrow space\ ws\ |\ tab\ ws\ |\ \epsilon\)
@@ -41,7 +41,7 @@ Define the production rules, \(P\), as the following:
\item \(l \rightarrow lf\ |\ cr\ lf\)
\end{enumerate}
Note that the use of spaces above is purely for visualization purposes (e.g., \(digit\ int\)
-does not actually have a space). Define the start symbol to be \(expr\). Define the unambiguous,
+does not actually have a space). Define the start symbol to be \(prog\). Define the unambiguous,
context-free grammar to be
\[ G = (N, \Sigma, P, prog). \]
Let \(\mathcal{L}(G)\) be the language generated from \(G\). When \(@\) is not immediately followed by a \(prev\),
@@ -50,12 +50,19 @@ expression. \(lf\) is the Unicode scalar value U+000A, \(cr\) is the Unicode sca
Unicode scalar value U+0020, \(tab\) is the Unicode scalar value U+0009, and \(\epsilon\) is the empty string. We
define \(\mathbb{Q} \subset L \subset \mathcal{L}(G)\) with \(\mathbb{Q}\) representing the field of rational
numbers such that \(L\) extends \(\mathbb{Q}\) with the ability to recall the previous one to eight \(store\)
-results as well as adds the unary operators \(||\), \(-\), and \(!\) as well as the binary operator \(\string^\) to
-mean absolute value, negation, factorial, and exponentiation respectively. Note that this means for \(mult / exp\),
-\(exp\) does not evaluate to 0. Similarly, \(term\string^exp\) is valid iff \(term\) evaluates to 1, \(term\) evaluates
-to 0 and \(exp\) evaluates to a non-negative rational number—\(0^{0}\) is defined to be 1—or \(term\) evaluates to any
-other rational number and \(exp\) evaluates to an integer. \(!\) is only defined for non-negative integers. \(@prev\) is
-only defined iff at least \(prev\) number of previous \(store\) expressions have been evaluated.
+results as well as adds the unary operators \(||\), \(-\), and \(!\) as well as the binary operators \(\string^\)
+and \(mod\) to mean absolute value, negation, factorial, exponentiation, and modulo respectively.
+
+Note that this means for \(mult / exp\), \(exp\) does not evaluate to 0. Similarly, \(term\string^exp\) is valid iff
+\(term\) evaluates to 1, \(term\) evaluates to 0 and \(exp\) evaluates to a non-negative rational number—\(0^{0}\) is
+defined to be 1—or \(term\) evaluates to any other rational number and \(exp\) evaluates to an integer. \(!\) is
+only defined for non-negative integers. \(@prev\) is only defined iff at least \(prev\) number of previous \(store\)
+expressions have been evaluated.
+
+\(mod\) is defined iff the left operand evaluates to an integer and the right operand evaluates to a non-zero
+integer. This operator returns the minimum non-negative integer, \(r\), that satisfies the equation
+\[ n\ mod\ m = r = n - q*m \]
+for \(n,q\in\mathbb{Z}\), \(m\in\mathbb{Z}\backslash\{0\}\), and \(r\in\mathbb{N}\).
It also adds the function \(round\) which rounds the passed expression to \(digit\)-number of fractional digits.
The function \(rand\) when passed no arguments generates a random 64-bit integer. When the function is passed two
@@ -69,11 +76,11 @@ From the above grammar, we see the expression precedence in descending order is
\item \(!\)
\item \(\string^\)
\item \(-\) (the unary negation operator)
- \item \(*\), \(/\)
+ \item \(*\), \(/\), \(mod\)
\item \(+\), \(-\)
\end{enumerate}
-with \(\string^\) being right-associative and the rest of the binary operators being left-associative. Last,
-for \(j \in \mathbb{N}\) and \(d_{j} \in \{0,1,2,3,4,5,6,7,8,9\}\subset \mathbb{Z}\), we have
+with \(\string^\) being right-associative and the rest of the binary operators being left-associative.
+Last, for \(j \in \mathbb{N}\) and \(d_{j} \in \{0,1,2,3,4,5,6,7,8,9\}\subset \mathbb{Z}\), we have
\begin{align*}
d_{0}d_{1}\cdots d_{n}.d_{n+1}\cdots d_{n+i} = (&d_{0}*10^{n} + d_{1}*10^{n-1} +\cdots + d_{n}*10^{0}\\
&+d_{n+1}*10^{-1} +\cdots + d_{n+i}*10^{-i})
diff --git a/src/cache.rs b/src/cache.rs
@@ -23,14 +23,14 @@ pub struct Cache<T, const N: usize> {
vals: [T; N],
/// The number of `T`s in our cache. Once
/// `N` number of `T`s have been cached, this will
- /// always return `N`.
+ /// always be `N`.
len: usize,
/// The index position of the next insert.
next_head: usize,
}
impl<T, const N: usize> Cache<T, N> {
/// Returns the number of items cached.
- /// This will always be `N` once at least
+ /// This will always return `N` once at least
/// `N` items have been cached.
#[inline]
pub const fn len(&self) -> usize {
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,7 +1,8 @@
-//! # `calc_lib`
-//! `calc_lib` is a library for basic rational number arithmetic using standard
-//! operator precedence and associativity. Internally, it is
-//! based on [`Ratio<T>`](https://docs.rs/num/latest/num/rational/struct.Ratio.html)
+//! # `calc_lib`
+//!
+//! `calc_lib` is a library for performing basic rational number arithmetic using standard operator precedence
+//! and associativity. Internally, it is based on
+//! [`Ratio<T>`](https://docs.rs/num/latest/num/rational/struct.Ratio.html)
//! and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html).
//!
//! ## Expressions
@@ -11,7 +12,7 @@
//! 2. `!`
//! 3. `^`
//! 4. `-` (unary negation operator)
-//! 5. `*`, `/`
+//! 5. `*`, `/`, `mod`
//! 6. `+`, `-`
//!
//! All binary operators are left-associative sans `^` which is right-associative.
@@ -28,9 +29,9 @@
//! will result in an error message.
//!
//! `^` is the exponentiation operator. The expression left of the operator can evaluate to any rational number;
-//! however the expression right of the operator must evaluate to an integer unless the expression on the left
-//! evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate to a
-//! non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
+//! however the expression right of the operator must evaluate to an integer or (+/-) 1/2 unless the expression on
+//! the left evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate
+//! to a non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
//! any rational number. Note that `0^0` is defined to be 1.
//!
//! The unary operator `-` represents negation.
@@ -38,6 +39,9 @@
//! The operators `*` and `/` represent multiplication and division respectively. Expressions right of `/`
//! must evaluate to any non-zero rational number; otherwise an error will be displayed.
//!
+//! The binary operator `mod` represents modulo such that *n mod m = r = n - m\*q* for *n,q ∈ ℤ, m ∈ ℤ\\{0}, and r ∈ ℕ*
+//! where *r* is the minimum non-negative solution.
+//!
//! The binary operators `+` and `-` represent addition and subtraction respectively.
//!
//! With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored.
@@ -56,20 +60,20 @@
//!
//! A number literal is a non-empty sequence of digits or a non-empty sequence of digits immediately followed by `.`
//! which is immediately followed by a non-empty sequence of digits (e.g., `134.901`). This means that number
-//! literals represent precisely all possible ratios of non-negative integers to non-negative integers who sole
-//! prime factors are 2 or 5. To represent all other rational numbers, the unary operator `-` and binary
-//! operator `/` must be used.
+//! literals represent precisely all rational numbers that are equivalent to a ratio of a non-negative integer
+//! to a positive integer whose sole prime factors are 2 or 5. To represent all other rational numbers, the unary
+//! operator `-` and binary operator `/` must be used.
//!
//! ## Empty expression
//!
//! The empty expression (i.e., expression that at most only consists of spaces and tabs) will return
-//! the result from the previous non-empty and non-store expression in *decimal* form using the minimum number of digits.
+//! the result from the previous non-(empty/store) expression in *decimal* form using the minimum number of digits.
//! In the event an infinite number of digits is required, it will be rounded to 9 fractional digits using normal rounding
//! rules first.
//!
//! ## Store expression
//!
-//! To store the result of the previous non-empty and non-store expression, one simply passes `s`. In addition to storing the
+//! To store the result of the previous non-(empty/store) expression, one simply passes `s`. In addition to storing the
//! result which will subsequently be available via `@`, it displays the result. At most 8 results can be stored at once;
//! at which point, results that are stored overwrite the oldest result.
//!
@@ -84,8 +88,9 @@
//! ## Character encoding
//!
//! All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`,
-//! `*`, `/`, `^`, `!`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>, <line feed>, <carriage return>,
-//! and `q`. Any other byte sequences are grammatically incorrect and will lead to an error message.
+//! `*`, `/`, `^`, `!`, `mod`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>,
+//! <line feed>, <carriage return>, and `q`. Any other byte sequences are grammatically incorrect and will
+//! lead to an error message.
//!
//! ## Errors
//!
@@ -102,7 +107,7 @@
//! For a more precise specification of the “calc language”, one can read the
//! [calc language specification](https://git.philomathiclife.com/calc_rational/lang.pdf).
#![no_std]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
#![deny(
unsafe_code,
unused,
@@ -116,7 +121,6 @@
#![allow(
clippy::arithmetic_side_effects,
clippy::blanket_clippy_restriction_lints,
- clippy::exhaustive_enums,
clippy::implicit_return,
clippy::integer_arithmetic,
clippy::into_iter_on_ref,
@@ -132,17 +136,19 @@ use alloc::vec::Vec;
use cache::Cache;
use core::convert;
use core::fmt::{self, Display, Formatter};
+use core::ops::Index;
use num_bigint::{BigInt, BigUint, Sign};
use num_integer::Integer;
use num_rational::Ratio;
-use num_traits::Pow;
#[cfg(feature = "rand")]
use num_traits::ToPrimitive;
+use num_traits::{Inv, Pow};
#[cfg(feature = "rand")]
use rand::{rngs::ThreadRng, RngCore};
use LangErr::{
- DivByZero, ExpDivByZero, ExpIsNotInt, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
- InvalidRound, InvalidStore, MissingTerm, NotEnoughPrevResults, NotNonNegIntFact, TrailingSyms,
+ DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
+ InvalidRound, InvalidStore, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
+ NotNonNegIntFact, SqrtDoesNotExist, TrailingSyms,
};
use O::{Empty, Eval, Exit, Store};
/// Fixed-sized cache that automatically overwrites the oldest data
@@ -154,6 +160,7 @@ pub mod cache;
/// generic associated types.
pub mod lending_iterator;
/// Error due to a language violation.
+#[non_exhaustive]
#[derive(Debug)]
pub enum LangErr {
/// The input began with a `q` but had non-whitespace
@@ -167,13 +174,20 @@ pub enum LangErr {
DivByZero(usize),
/// A sub-expression in the input would have led
/// to a rational number that was not 0 or 1 to be
- /// raised to a non-integer power.
- ExpIsNotInt(usize),
+ /// raised to a non-integer power that is not (+/-) 1/2.
+ ExpIsNotIntOrOneHalf(usize),
/// A sub-expression in the input would have led
/// to 0 being raised to a negative power which itself
/// would have led to a division by zero.
ExpDivByZero(usize),
/// A sub-expression in the input would have led
+ /// to a number modulo 0.
+ ModZero(usize),
+ /// A sub-expression in the input would have led
+ /// to the mod of two expressions with at least one
+ /// not being an integer.
+ ModIsNotInt(usize),
+ /// A sub-expression in the input would have led
/// to a non-integer factorial or a negative integer factorial.
NotNonNegIntFact(usize),
/// The input contained a non-empty sequence of digits followed
@@ -194,24 +208,23 @@ pub enum LangErr {
/// recall expression, absolute value expression, parenthetical
/// expression, or round expression.
MissingTerm(usize),
+ /// The expression that was passed to the square root does not have a solution
+ /// in the field of rational numbers.
+ SqrtDoesNotExist(usize),
/// The input started with a valid expression but was immediately followed
/// by symbols that could not be chained with the preceding expression.
TrailingSyms(usize),
/// The input contained an invalid random expression.
#[cfg(feature = "rand")]
- #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
InvalidRand(usize),
/// Error when attempting to generate a random number.
#[cfg(feature = "rand")]
- #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
RandErr(rand::Error),
/// Error when the second argument is less than first in the rand function.
#[cfg(feature = "rand")]
- #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
RandInvalidArgs(usize),
/// Error when there are no 64-bit integers in the interval passed to the random function.
#[cfg(feature = "rand")]
- #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
RandNoInts(usize),
}
impl Display for LangErr {
@@ -221,21 +234,24 @@ impl Display for LangErr {
InvalidStore => f.write_str("Invalid store expression. A store expression must be of the extended regex form: ^[ \\t]*s[ \\t]*$."),
InvalidQuit => f.write_str("Invalid quit expression. A quit expression must be of the extended regex form: ^[ \\t]*q[ \\t]*$."),
DivByZero(u) => write!(f, "Division by zero ending at position {u}."),
- ExpIsNotInt(u) => write!(f, "Non-integer exponent with a base that was not 0 or 1 ending at position {u}."),
+ ExpIsNotIntOrOneHalf(u) => write!(f, "Non-integer exponent that is not (+/-) 1/2 with a base that was not 0 or 1 ending at position {u}."),
ExpDivByZero(u) => write!(f, "Non-negative exponent with a base of 0 ending at position {u}."),
+ ModZero(u) => write!(f, "A number modulo 0 ending at position {u}."),
+ ModIsNotInt(u) => write!(f, "The modulo expression was applied to at least one non-integer ending at position {u}."),
NotNonNegIntFact(u) => write!(f, "Factorial of a rational number that was not a non-negative integer ending at position {u}."),
InvalidDec(u) => write!(f, "Invalid decimal literal expression ending at position {u}. A decimal literal expression must be of the extended regex form: [0-9]+(\\.[0-9]+)?."),
NotEnoughPrevResults(len) => write!(f, "There are only {len} previous results."),
InvalidAbs(u) => write!(f, "Invalid absolute value expression ending at position {u}. An absolute value expression is an addition expression enclosed in '||'."),
InvalidPar(u) => write!(f, "Invalid parenthetical expression ending at position {u}. A parenthetical expression is an addition expression enclosed in '()'."),
- InvalidRound(u) => write!(f, "Invalid round expression ending at position {u}. A round expression is of the form 'round(<add expression>, digit)'"),
+ InvalidRound(u) => write!(f, "Invalid round expression ending at position {u}. A round expression is of the form 'round(<mod expression>, digit)'"),
+ SqrtDoesNotExist(u) => write!(f, "The square root of the passed expression does not have a solution in the field of rational numbers ending at position {u}."),
#[cfg(not(feature = "rand"))]
MissingTerm(u) => write!(f, "Missing terminal expression at position {u}. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, or round expression."),
#[cfg(feature = "rand")]
MissingTerm(u) => write!(f, "Missing terminal expression at position {u}. A terminal expression is a decimal literal expression, recall expression, absolute value expression, parenthetical expression, round expression, or rand expression."),
TrailingSyms(u) => write!(f, "Trailing symbols starting at position {u}."),
#[cfg(feature = "rand")]
- Self::InvalidRand(u) => write!(f, "Invalid rand expression ending at position {u}. A rand expression is of the form 'rand()' or 'rand(<add expression>, <add expression>)'."),
+ Self::InvalidRand(u) => write!(f, "Invalid rand expression ending at position {u}. A rand expression is of the form 'rand()' or 'rand(<mod expression>, <mod expression>)'."),
#[cfg(feature = "rand")]
Self::RandErr(ref e) => e.fmt(f),
#[cfg(feature = "rand")]
@@ -246,6 +262,7 @@ impl Display for LangErr {
}
}
/// A successful evaluation of an input.
+#[allow(clippy::exhaustive_enums)]
#[derive(Debug)]
pub enum O<'a> {
/// The input only contained whitespace.
@@ -361,7 +378,7 @@ impl<'a> Display for O<'a> {
}
}
/// Evaluates the supplied input.
-pub struct Evaluator<'input, 'cache, 'prev, 'exp> {
+pub struct Evaluator<'input, 'cache, 'prev, 'scratch> {
/// The input to be evaluated.
utf8: &'input [u8],
/// The index within `utf8` that evaluation needs to continue.
@@ -373,28 +390,24 @@ pub struct Evaluator<'input, 'cache, 'prev, 'exp> {
cache: &'cache mut Cache<Ratio<BigInt>, 8>,
/// The last result.
prev: &'prev mut Option<Ratio<BigInt>>,
- /// Buffer used to evaluate exponentiation sub-expressions.
- /// We require buffering exponentiation sub-expressions due
- /// to their being right-associative. Instead of repeatedly
- /// creating temporary internal buffers, we rely on a supplied
- /// buffer that can be re-used across multiple evaluations.
- exp_cache: &'exp mut Vec<Ratio<BigInt>>,
+ /// Buffer used to evaluate right-associative sub-expressions.
+ scratch: &'scratch mut Vec<Ratio<BigInt>>,
}
-impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
+impl<'input, 'cache, 'prev, 'scratch> Evaluator<'input, 'cache, 'prev, 'scratch> {
#[inline]
- /// Creates an `Evaluator<'input, 'cache, 'prev, 'exp>` based on the supplied arguments.
+ /// Creates an `Evaluator<'input, 'cache, 'prev, 'scratch>` based on the supplied arguments.
pub fn new(
utf8: &'input [u8],
cache: &'cache mut Cache<Ratio<BigInt>, 8>,
prev: &'prev mut Option<Ratio<BigInt>>,
- exp_cache: &'exp mut Vec<Ratio<BigInt>>,
- ) -> Evaluator<'input, 'cache, 'prev, 'exp> {
+ scratch: &'scratch mut Vec<Ratio<BigInt>>,
+ ) -> Evaluator<'input, 'cache, 'prev, 'scratch> {
Self {
utf8,
i: 0,
cache,
prev,
- exp_cache,
+ scratch,
}
}
/// Evaluates the input consuming the `Evaluator<'input, 'cache, 'exp>`.
@@ -494,10 +507,13 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
/// This function is used for both multiplication and division operations which
/// themselves are based on negation expressions.
#[inline]
+ #[allow(clippy::else_if_without_else)]
fn get_mults(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut left = self.get_neg()?;
let mut right;
let mut j;
+ let mut mod_val;
+ let mut numer;
self.consume_ws();
while let Some(i) = self.utf8.get(self.i) {
j = *i;
@@ -514,6 +530,34 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
return Err(DivByZero(self.i));
}
left /= right;
+ } else if let Some(k) = self.utf8.get(self.i..self.i.saturating_add(3)) {
+ if k == b"mod" {
+ if !left.is_integer() {
+ return Err(ModIsNotInt(self.i));
+ }
+ self.i += 3;
+ self.consume_ws();
+ right = self.get_neg()?;
+ if !right.is_integer() {
+ return Err(ModIsNotInt(self.i));
+ }
+ numer = right.numer();
+ if numer.sign() == Sign::NoSign {
+ return Err(ModZero(self.i));
+ }
+ mod_val = left.numer() % numer;
+ left = Ratio::from_integer(if mod_val.sign() == Sign::Minus {
+ if numer.sign() == Sign::Minus {
+ mod_val - numer
+ } else {
+ mod_val + numer
+ }
+ } else {
+ mod_val
+ });
+ } else {
+ break;
+ }
} else {
break;
}
@@ -537,18 +581,64 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
self.get_exps()
.map(|val| if count & 1 == 0 { val } else { -val })
}
+ /// Gets the square root of value so long as a solution exists.
+ #[inline]
+ #[allow(clippy::unwrap_used)]
+ fn sqrt(val: Ratio<BigInt>) -> Option<Ratio<BigInt>> {
+ /// Returns the square root of `n` if one exists; otherwise
+ /// returns `None`.
+ // MUST NOT pass 0.
+ #[inline]
+ #[allow(clippy::suspicious_operation_groupings)]
+ fn calc(n: &BigUint) -> Option<BigUint> {
+ let mut shift = n.bits();
+ shift += shift & 1;
+ let mut result = BigUint::new(Vec::new());
+ let one = BigUint::new(vec![1]);
+ let zero = BigUint::new(Vec::new());
+ loop {
+ shift -= 2;
+ result <<= 1u32;
+ result |= &one;
+ result ^= if &result * &result > (n >> shift) {
+ &one
+ } else {
+ &zero
+ };
+ if shift == 0 {
+ break (&result * &result == *n).then_some(result);
+ }
+ }
+ }
+ let numer = val.numer();
+ if numer.sign() == Sign::NoSign {
+ Some(val)
+ } else {
+ numer.try_into().map_or_else(
+ |_| None,
+ |num| {
+ calc(&num).and_then(|n| {
+ calc(&val.denom().try_into().unwrap())
+ .map(|d| Ratio::new(n.into(), d.into()))
+ })
+ },
+ )
+ }
+ }
/// Evaluates exponentiation expressions as defined in the calc language.
/// This function is based on negation expressions.
#[inline]
- #[allow(clippy::unwrap_used, clippy::unwrap_in_result)]
fn get_exps(&mut self) -> Result<Ratio<BigInt>, LangErr> {
let mut t = self.get_fact()?;
- let ix = self.exp_cache.len();
+ let ix = self.scratch.len();
let mut prev;
let mut numer;
- self.exp_cache.push(t);
+ self.scratch.push(t);
self.consume_ws();
let mut j;
+ let one = BigInt::new(Sign::Plus, vec![1]);
+ let min_one = BigInt::new(Sign::Minus, vec![1]);
+ let two = BigInt::new(Sign::Plus, vec![2]);
while let Some(i) = self.utf8.get(self.i) {
j = *i;
self.consume_ws();
@@ -556,46 +646,62 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
self.i += 1;
self.consume_ws();
t = self.get_neg()?;
- // This is safe since we always push a value into it
- // before entering the loop.
- prev = self.exp_cache.last().unwrap();
+ // Safe since we always push at least one value, and we always
+ // return immediately once we encounter an error.
+ prev = self.scratch.index(self.scratch.len() - 1);
numer = prev.numer();
// Equiv to checking if prev is 0.
if numer.sign() == Sign::NoSign {
if t.numer().sign() == Sign::Minus {
+ self.scratch.clear();
return Err(ExpDivByZero(self.i));
}
- self.exp_cache.push(t);
+ self.scratch.push(t);
} else if prev.is_integer() {
- if numer == &BigInt::new(Sign::Plus, vec![1]) {
- // 1 raised to anything is 1, so we don't bother
- // storing the exponent.
- } else if t.is_integer() {
- self.exp_cache.push(t);
+ let t_numer = t.numer();
+ // 1 raised to anything is 1, so we don't bother
+ // storing the exponent.
+ if *numer == one {
+ } else if t.is_integer()
+ || ((*t_numer == one || *t_numer == min_one) && *t.denom() == two)
+ {
+ self.scratch.push(t);
} else {
- return Err(ExpIsNotInt(self.i));
+ self.scratch.clear();
+ return Err(ExpIsNotIntOrOneHalf(self.i));
}
- } else if t.is_integer() {
- self.exp_cache.push(t);
+ } else if t.is_integer()
+ || ((*t.numer() == one || *t.numer() == min_one) && *t.denom() == two)
+ {
+ self.scratch.push(t);
} else {
- return Err(ExpIsNotInt(self.i));
+ self.scratch.clear();
+ return Err(ExpIsNotIntOrOneHalf(self.i));
}
} else {
break;
}
}
- self.exp_cache.drain(ix..).try_rfold(
- Ratio::from_integer(BigInt::new(Sign::Plus, vec![1])),
- |exp, base| {
+ self.scratch
+ .drain(ix..)
+ .try_rfold(Ratio::from_integer(one.clone()), |exp, base| {
if exp.is_integer() {
Ok(base.pow(exp.numer()))
} else if base.numer().sign() == Sign::NoSign {
Ok(base)
+ } else if *exp.denom() == two {
+ if *exp.numer() == one {
+ Self::sqrt(base).map_or_else(|| Err(SqrtDoesNotExist(self.i)), Ok)
+ } else if *exp.numer() == min_one {
+ Self::sqrt(base)
+ .map_or_else(|| Err(SqrtDoesNotExist(self.i)), |v| Ok(v.inv()))
+ } else {
+ Err(ExpIsNotIntOrOneHalf(self.i))
+ }
} else {
- Err(ExpIsNotInt(self.i))
+ Err(ExpIsNotIntOrOneHalf(self.i))
}
- },
- )
+ })
}
/// Evaluates factorial expressions as defined in the calc language.
/// This function is based on terminal expressions.
@@ -623,8 +729,8 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
// We can make a copy of self.i here, or call map_or instead
// of map_or_else.
let i = self.i;
- t.numer().to_biguint().map_or_else(
- || Err(NotNonNegIntFact(i)),
+ t.numer().try_into().map_or_else(
+ |_| Err(NotNonNegIntFact(i)),
|val| {
let mut factorial = fact(val);
while let Some(b2) = self.utf8.get(self.i) {
@@ -650,45 +756,50 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
}
/// Evaluates terminal expressions as defined in the calc language.
/// This function is based on number literal expressions, parenthetical expressions,
- /// recall expressions, absolute value expressions, and round expressions.
+ /// recall expressions, absolute value expressions, round expressions, and possibly
+ /// rand expressions if that feature is enabled.
#[inline]
- #[allow(clippy::option_if_let_else)]
fn get_term(&mut self) -> Result<Ratio<BigInt>, LangErr> {
- match self.get_rational() {
- Ok(o) => match o {
- Some(r) => Ok(r),
- None => match self.get_par() {
- Ok(o2) => match o2 {
- Some(r2) => Ok(r2),
- None => match self.get_recall() {
- Ok(o3) => match o3 {
- Some(r3) => Ok(r3.clone()),
- None => match self.get_abs() {
- Ok(o4) => match o4 {
- Some(r4) => Ok(r4),
- #[cfg(feature = "rand")]
- None => self
- .get_round()
- .and_then(|o5| o5.map_or_else(|| self.get_rand(), Ok)),
- #[cfg(not(feature = "rand"))]
- None => self.get_round().and_then(|o5| {
- o5.map_or_else(|| Err(MissingTerm(self.i)), Ok)
- }),
- },
- Err(e4) => Err(e4),
- },
+ self.get_rational().map_or_else(Err, |o| {
+ o.map_or_else(
+ || {
+ self.get_par().map_or_else(Err, |o2| {
+ o2.map_or_else(
+ || {
+ self.get_recall().map_or_else(Err, |o3| {
+ o3.map_or_else(
+ || {
+ self.get_abs().map_or_else(Err, |o4| {
+ o4.map_or_else(
+ || {
+ self.get_round().and_then(|o5| {
+ o5.map_or_else(
+ #[cfg(not(feature = "rand"))]
+ || Err(MissingTerm(self.i)),
+ #[cfg(feature = "rand")]
+ || self.get_rand(),
+ Ok,
+ )
+ })
+ },
+ Ok,
+ )
+ })
+ },
+ Ok,
+ )
+ })
},
- Err(e3) => Err(e3),
- },
- },
- Err(e2) => Err(e2),
+ Ok,
+ )
+ })
},
- },
- Err(e) => Err(e),
- }
+ Ok,
+ )
+ })
}
/// Generates a random 64-bit integer.
- /// This function is based on addition expressions.
+ /// This function is based on modulo expressions.
/// This is the last terminal expression attempted when needing
/// a terminal expression; as a result, it is the only terminal expression
/// that does not return an `Option`.
@@ -813,7 +924,7 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
}
}
/// Rounds a value to the specified number of fractional digits.
- /// This function is based on addition expressions.
+ /// This function is based on modulo expressions.
#[inline]
fn get_round(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i..self.i.saturating_add(6)) else {
@@ -863,7 +974,7 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
}
}
/// Evaluates absolute value expressions as defined in the calc language.
- /// This function is based on addition expressions.
+ /// This function is based on modulo expressions.
#[inline]
fn get_abs(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
@@ -893,9 +1004,13 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
}
}
/// Evaluates recall expressions as defined in the calc language.
+ // This does not return a Result<Option<&Ratio<BigInt>>, LangErr>
+ // since the only place this function is called is in get_term which
+ // would end up needing to clone the Ratio anyway. By not forcing
+ // get_term to clone, it can rely on map_or_else over match expressions.
#[inline]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
- fn get_recall(&mut self) -> Result<Option<&Ratio<BigInt>>, LangErr> {
+ fn get_recall(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
return Ok(None)
};
@@ -912,14 +1027,14 @@ impl<'input, 'cache, 'prev, 'exp> Evaluator<'input, 'cache, 'prev, 'exp> {
}))
.map_or_else(
|| Err(NotEnoughPrevResults(self.cache.len() as u8)),
- |p| Ok(Some(p)),
+ |p| Ok(Some(p.clone())),
)
} else {
Ok(None)
}
}
/// Evaluates parenthetical expressions as defined in the calc language.
- /// This function is based on addition expressions.
+ /// This function is based on modulo expressions.
#[inline]
fn get_par(&mut self) -> Result<Option<Ratio<BigInt>>, LangErr> {
let Some(b) = self.utf8.get(self.i) else {
@@ -1056,9 +1171,9 @@ extern crate std;
#[cfg(feature = "std")]
use std::io::{BufRead, Error};
#[cfg(feature = "std")]
-#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
/// Error returned from [`EvalIter`] when
/// an expression has an error.
+#[allow(clippy::exhaustive_enums)]
#[derive(Debug)]
pub enum E {
/// Error containing [`Error`] which is returned
@@ -1082,7 +1197,6 @@ impl Display for E {
#[cfg(feature = "std")]
use crate::lending_iterator::LendingIterator;
#[cfg(feature = "std")]
-#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl<R> LendingIterator for EvalIter<R>
where
R: BufRead,
@@ -1122,8 +1236,9 @@ where
#[cfg(test)]
mod tests {
use super::LangErr::{
- DivByZero, ExpDivByZero, ExpIsNotInt, InvalidAbs, InvalidDec, InvalidPar, InvalidQuit,
- InvalidRound, MissingTerm, NotEnoughPrevResults, NotNonNegIntFact, TrailingSyms,
+ DivByZero, ExpDivByZero, ExpIsNotIntOrOneHalf, InvalidAbs, InvalidDec, InvalidPar,
+ InvalidQuit, InvalidRound, MissingTerm, ModIsNotInt, ModZero, NotEnoughPrevResults,
+ NotNonNegIntFact, TrailingSyms,
};
use super::*;
#[test]
@@ -1504,7 +1619,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// Invalid characters are ignored at this stage.
assert!(
@@ -1512,7 +1627,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// 0 is not a valid stored value only 1-8 are.
assert!(
@@ -1520,7 +1635,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// 9 is not a valid stored value only 1-8 are.
assert!(
@@ -1528,7 +1643,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// Spaces are not cleaned; otherwise this would error since we only have 1 stored value.
assert!(
@@ -1536,7 +1651,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// Tabs are not cleaned; otherwise this would error since we only have 1 stored value.
assert!(
@@ -1544,7 +1659,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// One digits are looked at so this is not @<ten>.
assert!(
@@ -1552,7 +1667,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// Invalid recall expression since there is only one stored result.
assert!(
@@ -1576,14 +1691,14 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
);
assert!(
Evaluator::new(b"@2", &mut cache, &mut prev, &mut Vec::new())
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
);
// Invalid recall expression since there are only three stored results.
assert!(
@@ -1614,7 +1729,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(
+ == Ratio::from_integer(BigInt::from_biguint(
Sign::Plus,
BigUint::new(vec![(b'9' - i) as u32])
))
@@ -1626,7 +1741,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))
);
Evaluator::new(b"9\r\n", &mut cache, &mut prev, &mut Vec::new())
.evaluate()
@@ -1642,7 +1757,7 @@ mod tests {
.get_recall()
.unwrap()
.unwrap()
- == &Ratio::from_integer(BigInt::from_biguint(
+ == Ratio::from_integer(BigInt::from_biguint(
Sign::Plus,
BigUint::new(vec![((b'9' + 1) - i) as u32])
))
@@ -2316,6 +2431,20 @@ mod tests {
BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8]))
)
);
+ assert!(
+ Evaluator::new(
+ b"(4/9)^(-1/2)",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_exps()
+ .unwrap()
+ == Ratio::new(
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![3])),
+ BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2]))
+ )
+ );
// Error since 0 cannot be raised to a negative power.
assert!(
match Evaluator::new(b"0^(-1)", &mut Cache::new(), &mut None, &mut Vec::new())
@@ -2326,13 +2455,23 @@ mod tests {
_ => false,
}
);
- // Error anything other than 0 or 1 cannot be raised to a non-integer power.
+ // Error since anything other than 0 or 1 cannot be raised to a non-integer power or (+/-) 1/2.
+ assert!(
+ match Evaluator::new(b"2^(1/3)", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_exps()
+ .unwrap_err()
+ {
+ ExpIsNotIntOrOneHalf(i) => i == 7,
+ _ => false,
+ }
+ );
+ // When exponent is (+/-) 1/2, base has to be the square of a rational number.
assert!(
match Evaluator::new(b"2^(1/2)", &mut Cache::new(), &mut None, &mut Vec::new())
.get_exps()
.unwrap_err()
{
- ExpIsNotInt(i) => i == 7,
+ SqrtDoesNotExist(i) => i == 7,
_ => false,
}
);
@@ -2553,6 +2692,46 @@ mod tests {
_ => false,
}
);
+ assert!(
+ Evaluator::new(
+ b"4.0\t mod 6",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults()
+ .unwrap()
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![4])),)
+ );
+ assert!(
+ Evaluator::new(b"5 mod 3", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap()
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
+ );
+ assert!(
+ Evaluator::new(b"-5 mod 3", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap()
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ );
+ assert!(
+ Evaluator::new(b"5 mod -3", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap()
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
+ );
+ assert!(
+ Evaluator::new(
+ b"-5 mod\t -3",
+ &mut Cache::new(),
+ &mut None,
+ &mut Vec::new()
+ )
+ .get_mults()
+ .unwrap()
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![1])))
+ );
// Cannot divide by 0.
assert!(
match Evaluator::new(b"2/0", &mut Cache::new(), &mut None, &mut Vec::new())
@@ -2563,6 +2742,34 @@ mod tests {
_ => false,
}
);
+ assert!(
+ match Evaluator::new(b"2 mod 0", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap_err()
+ {
+ ModZero(i) => i == 7,
+ _ => false,
+ }
+ );
+ // Right and left operands of mod must be integers.
+ assert!(
+ match Evaluator::new(b"3.2 mod 1", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap_err()
+ {
+ ModIsNotInt(i) => i == 4,
+ _ => false,
+ }
+ );
+ assert!(
+ match Evaluator::new(b"3 mod 3.2", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap_err()
+ {
+ ModIsNotInt(i) => i == 9,
+ _ => false,
+ }
+ );
// multiplication has lower precedence than exponentiation.
assert!(
Evaluator::new(b"2*2^2", &mut Cache::new(), &mut None, &mut Vec::new())
@@ -2576,6 +2783,12 @@ mod tests {
.unwrap()
== Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![2])))
);
+ assert!(
+ Evaluator::new(b"8 mod 3^2", &mut Cache::new(), &mut None, &mut Vec::new())
+ .get_mults()
+ .unwrap()
+ == Ratio::from_integer(BigInt::from_biguint(Sign::Plus, BigUint::new(vec![8])))
+ );
}
#[test]
fn add() {
diff --git a/src/main.rs b/src/main.rs
@@ -1,6 +1,7 @@
-//! # `calc`
-//! `calc` is a CLI calculator for basic rational number arithmetic using standard
-//! operator precedence and associativity. Internally, it is
+//! # `calc`
+//!
+//! `calc` is a CLI calculator for basic
+//! rational number arithmetic using standard operator precedence and associativity. Internally, it is
//! based on [`Ratio<T>`](https://docs.rs/num/latest/num/rational/struct.Ratio.html)
//! and [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html).
//!
@@ -40,6 +41,18 @@
//! > 939435294927814822
//! rand(1+9,10!)
//! > 2660936
+//! 1+4 mod 2 + 1
+//! > 2
+//! -5 mod 2
+//! > 1
+//! -5 mod -2
+//! > 1
+//! 5 mod -2
+//! > 1
+//! 9^0.5
+//! > 3
+//! (4/9)^(-1/2)
+//! > 3/2
//! q
//! [zack@laptop ~]$
//! ```
@@ -51,7 +64,7 @@
//! 2. `!`
//! 3. `^`
//! 4. `-` (unary negation operator)
-//! 5. `*`, `/`
+//! 5. `*`, `/`, `mod`
//! 6. `+`, `-`
//!
//! All binary operators are left-associative sans `^` which is right-associative.
@@ -68,9 +81,9 @@
//! will result in an error message.
//!
//! `^` is the exponentiation operator. The expression left of the operator can evaluate to any rational number;
-//! however the expression right of the operator must evaluate to an integer unless the expression on the left
-//! evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate to a
-//! non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
+//! however the expression right of the operator must evaluate to an integer or (+/-) 1/2 unless the expression on
+//! the left evaluates to `0` or `1`. In the event of the former, the expression right of the operator must evaluate
+//! to a non-negative rational number. In the event of the latter, the expression right of the operator can evaluate to
//! any rational number. Note that `0^0` is defined to be 1.
//!
//! The unary operator `-` represents negation.
@@ -78,6 +91,9 @@
//! The operators `*` and `/` represent multiplication and division respectively. Expressions right of `/`
//! must evaluate to any non-zero rational number; otherwise an error will be displayed.
//!
+//! The binary operator `mod` represents modulo such that *n mod m = r = n - m\*q* for *n,q ∈ ℤ, m ∈ ℤ\\{0}, and r ∈ ℕ*
+//! where *r* is the minimum non-negative solution.
+//!
//! The binary operators `+` and `-` represent addition and subtraction respectively.
//!
//! With the aforementioned exception of `!`, all spaces and tabs before and after operators are ignored.
@@ -96,20 +112,20 @@
//!
//! A number literal is a non-empty sequence of digits or a non-empty sequence of digits immediately followed by `.`
//! which is immediately followed by a non-empty sequence of digits (e.g., `134.901`). This means that number
-//! literals represent precisely all possible ratios of non-negative integers to non-negative integers who sole
-//! prime factors are 2 or 5. To represent all other rational numbers, the unary operator `-` and binary
-//! operator `/` must be used.
+//! literals represent precisely all rational numbers that are equivalent to a ratio of a non-negative integer
+//! to a positive integer whose sole prime factors are 2 or 5. To represent all other rational numbers, the unary
+//! operator `-` and binary operator `/` must be used.
//!
//! ## Empty expression
//!
//! The empty expression (i.e., expression that at most only consists of spaces and tabs) will return
-//! the result from the previous non-empty and non-store expression in *decimal* form using the minimum number of digits.
+//! the result from the previous non-(empty/store) expression in *decimal* form using the minimum number of digits.
//! In the event an infinite number of digits is required, it will be rounded to 9 fractional digits using normal rounding
//! rules first.
//!
//! ## Store expression
//!
-//! To store the result of the previous non-empty and non-store expression, one simply passes `s`. In addition to storing the
+//! To store the result of the previous non-(empty/store) expression, one simply passes `s`. In addition to storing the
//! result which will subsequently be available via `@`, it displays the result. At most 8 results can be stored at once;
//! at which point, results that are stored overwrite the oldest result.
//!
@@ -124,8 +140,9 @@
//! ## Character encoding
//!
//! All inputs must only contain the ASCII encoding of the following Unicode scalar values: `0`-`9`, `.`, `+`, `-`,
-//! `*`, `/`, `^`, `!`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>, <line feed>, <carriage return>,
-//! and `q`. Any other byte sequences are grammatically incorrect and will lead to an error message.
+//! `*`, `/`, `^`, `!`, `mod`, `|`, `(`, `)`, `round`, `rand`, `,`, `@`, `s`, <space>, <tab>,
+//! <line feed>, <carriage return>, and `q`. Any other byte sequences are grammatically incorrect and will
+//! lead to an error message.
//!
//! ## Errors
//!