#include <Arduino.h>
#include <math.h>
// --- High-Precision Constants ---
#define PI_CONST 3.141592653589793
#define E_CONST 2.718281828459045
// --- Stack Size Configuration ---
#define STACK_SIZE 100
// =================================================================
// NEW: Variable Support
// =================================================================
const char variableNames[] = "xyzuvw";
double variableValues[6]; // Holds values for x, y, z, u, v, w
// Helper function to check if a character is a supported variable
bool isVariable(char c) {
for (int i = 0; i < strlen(variableNames); i++) {
if (variableNames[i] == c) {
return true;
}
}
return false;
}
// Helper function to get the storage index for a variable
int getVariableIndex(char c) {
for (int i = 0; i < strlen(variableNames); i++) {
if (variableNames[i] == c) {
return i;
}
}
return -1; // Not found
}
// =================================================================
// 1. Character Stack for Shunting-Yard
// =================================================================
class CharStack {
public:
CharStack(int size = STACK_SIZE) {
maxSize = size;
stackArray = new String[maxSize];
top = -1;
}
~CharStack() { delete[] stackArray; }
void push(String value) {
if (!isFull()) stackArray[++top] = value;
else Serial.println("Error: Operator stack overflow!");
}
String pop() {
if (!isEmpty()) return stackArray[top--];
return "";
}
String peek() {
if (!isEmpty()) return stackArray[top];
return "";
}
bool isEmpty() { return top == -1; }
bool isFull() { return top == maxSize - 1; }
private:
String* stackArray;
int top;
int maxSize;
};
// =================================================================
// 2. Double/Float Stack for Postfix Evaluation
// =================================================================
class FloatStack {
public:
FloatStack(int size = STACK_SIZE) {
maxSize = size;
stackArray = new double[maxSize];
top = -1;
}
~FloatStack() { delete[] stackArray; }
void push(double value) {
if (!isFull()) stackArray[++top] = value;
else Serial.println("Error: Value stack overflow!");
}
double pop() {
if (!isEmpty()) return stackArray[top--];
return NAN;
}
double peek() {
if (!isEmpty()) return stackArray[top];
return NAN;
}
bool isEmpty() { return top == -1; }
bool isFull() { return top == maxSize - 1; }
private:
double* stackArray;
int top;
int maxSize;
};
// =================================================================
// 3. Main Calculator Logic Class
// =================================================================
class Calculator {
public:
String toPostfix(String infix);
double evaluatePostfix(String postfix, bool isRadians);
private:
int getPrecedence(String op);
bool isFunction(String token);
bool isOperator(char c);
String handleUnary(String infix);
double factorial(int n);
double power(double base, double exp);
double asinh(double x);
double acosh(double x);
double atanh(double x);
};
// --- Calculator Method Implementations ---
String Calculator::handleUnary(String infix) {
infix.replace(" ", "");
if (infix.startsWith("-")) infix = "0" + infix;
infix.replace("(-", "(0-");
infix.replace("(+", "(");
return infix;
}
int Calculator::getPrecedence(String op) {
if (op == "!") return 5;
if (isFunction(op)) return 4;
if (op == "^") return 3;
if (op == "*" || op == "/" || op == "%") return 2;
if (op == "+" || op == "-") return 1;
return 0;
}
bool Calculator::isFunction(String token) {
const char* functions[] = { "sin", "cos", "tan", "asin", "acos", "atan", "sind", "cosd", "tand", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", "ln", "log", "exp", "antilog", "sqrt" };
for (const char* func : functions) {
if (token.equalsIgnoreCase(func)) return true;
}
return false;
}
bool Calculator::isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '^' || c == '!';
}
String Calculator::toPostfix(String infix) {
infix = handleUnary(infix);
String postfix = "";
CharStack operators(STACK_SIZE);
for (int i = 0; i < infix.length(); ) {
char c = infix.charAt(i);
if (isDigit(c) || c == '.') {
String number = "";
while (i < infix.length() && (isDigit(infix.charAt(i)) || infix.charAt(i) == '.')) {
number += infix.charAt(i++);
}
postfix += number + " ";
continue;
}
if (c == 'p' && infix.substring(i, i + 2).equalsIgnoreCase("pi")) {
postfix += "pi "; i += 2; continue;
}
if (c == 'e' && (i + 1 == infix.length() || !isAlpha(infix.charAt(i + 1)))) {
postfix += "e "; i++; continue;
}
// --- NEW: Handle variables ---
if (isVariable(c)) {
postfix += String(c) + " ";
i++;
continue;
}
// -------------------------
if (isAlpha(c)) {
String func = "";
while (i < infix.length() && isAlpha(infix.charAt(i))) {
func += infix.charAt(i++);
}
if (isFunction(func)) operators.push(func);
continue;
}
if (c == '(') {
operators.push("("); i++; continue;
}
if (c == ')') {
while (!operators.isEmpty() && operators.peek() != "(") {
postfix += operators.pop() + " ";
}
operators.pop();
if (!operators.isEmpty() && isFunction(operators.peek())) {
postfix += operators.pop() + " ";
}
i++; continue;
}
if (isOperator(c)) {
String op1 = String(c);
while (!operators.isEmpty() && operators.peek() != "(" &&
((getPrecedence(op1) < getPrecedence(operators.peek())) ||
(getPrecedence(op1) == getPrecedence(operators.peek()) && op1 != "^"))) {
postfix += operators.pop() + " ";
}
operators.push(op1);
i++; continue;
}
i++;
}
while (!operators.isEmpty()) {
postfix += operators.pop() + " ";
}
postfix.trim();
return postfix;
}
double Calculator::evaluatePostfix(String postfix, bool isRadians) {
FloatStack values(STACK_SIZE);
int currentPos = 0;
while (currentPos < postfix.length()) {
int nextSpace = postfix.indexOf(' ', currentPos);
if (nextSpace == -1) nextSpace = postfix.length();
String token = postfix.substring(currentPos, nextSpace);
token.trim();
currentPos = nextSpace + 1;
if (token.length() == 0) continue;
if (isDigit(token.charAt(0)) || (token.charAt(0) == '-' && token.length() > 1)) {
values.push(token.toDouble());
} else if (token.equalsIgnoreCase("pi")) {
values.push(PI_CONST);
} else if (token.equalsIgnoreCase("e")) {
values.push(E_CONST);
}
// --- NEW: Substitute variable's value ---
else if (token.length() == 1 && isVariable(token.charAt(0))) {
values.push(variableValues[getVariableIndex(token.charAt(0))]);
}
// ----------------------------------------
else {
if (isFunction(token)) {
double val = values.pop();
double result = 0;
if (token.equalsIgnoreCase("sin")) result = isRadians ? sin(val) : sin(val * PI_CONST / 180.0);
else if (token.equalsIgnoreCase("cos")) result = isRadians ? cos(val) : cos(val * PI_CONST / 180.0);
else if (token.equalsIgnoreCase("tan")) result = isRadians ? tan(val) : tan(val * PI_CONST / 180.0);
else if (token.equalsIgnoreCase("asin")) result = isRadians ? asin(val) : asin(val) * 180.0 / PI_CONST;
else if (token.equalsIgnoreCase("acos")) result = isRadians ? acos(val) : acos(val) * 180.0 / PI_CONST;
else if (token.equalsIgnoreCase("atan")) result = isRadians ? atan(val) : atan(val) * 180.0 / PI_CONST;
else if (token.equalsIgnoreCase("sind")) result = sin(val * PI_CONST / 180.0);
else if (token.equalsIgnoreCase("cosd")) result = cos(val * PI_CONST / 180.0);
else if (token.equalsIgnoreCase("tand")) result = tan(val * PI_CONST / 180.0);
else if (token.equalsIgnoreCase("sinh")) result = sinh(val);
else if (token.equalsIgnoreCase("cosh")) result = cosh(val);
else if (token.equalsIgnoreCase("tanh")) result = tanh(val);
else if (token.equalsIgnoreCase("asinh")) result = asinh(val);
else if (token.equalsIgnoreCase("acosh")) result = acosh(val);
else if (token.equalsIgnoreCase("atanh")) result = atanh(val);
else if (token.equalsIgnoreCase("ln")) result = log(val);
else if (token.equalsIgnoreCase("log")) result = log10(val);
else if (token.equalsIgnoreCase("exp")) result = exp(val);
else if (token.equalsIgnoreCase("antilog")) result = power(10, val);
else if (token.equalsIgnoreCase("sqrt")) result = sqrt(val);
values.push(result);
} else {
if (token == "!") {
values.push(factorial((int)values.pop()));
} else {
double val2 = values.pop();
double val1 = values.pop();
if (token == "+") values.push(val1 + val2);
else if (token == "-") values.push(val1 - val2);
else if (token == "*") values.push(val1 * val2);
else if (token == "/") values.push(val1 / val2);
else if (token == "%") values.push(fmod(val1, val2));
else if (token == "^") values.push(power(val1, val2));
}
}
}
}
return values.pop();
}
// --- Mathematical Function Implementations ---
double Calculator::factorial(int n) { if (n < 0) return NAN; if (n == 0) return 1; double r = 1; for (int i = 1; i <= n; i++) r *= i; return r; }
double Calculator::power(double b, double e) { if (e == 0) return 1; if (b == 0) return 0; if (floor(e) == e && e > 0) { long i_e = (long)e; double r = 1.0; while (i_e > 0) { if (i_e % 2 == 1) r *= b; b *= b; i_e /= 2; } return r; } return pow(b, e); }
double Calculator::asinh(double x) { return log(x + sqrt(x * x + 1.0)); }
double Calculator::acosh(double x) { if (x < 1.0) return NAN; return log(x + sqrt(x * x - 1.0)); }
double Calculator::atanh(double x) { if (abs(x) >= 1.0) return NAN; return 0.5 * log((1.0 + x) / (1.0 - x)); }
// =================================================================
// 4. Arduino Main Setup and Loop
// =================================================================
Calculator calculator;
bool isRadiansMode = true;
void setup() {
Serial.begin(9600);
while (!Serial) { ; }
// Initialize variable values to 0
for(int i = 0; i < 6; i++) {
variableValues[i] = 0.0;
}
Serial.println("\n--- Arduino Scientific Calculator ---");
Serial.println("Supports: +, -, *, /, %, ^, !, ()");
Serial.println("Functions: sin, cos, tan, ln, log, sqrt, etc.");
Serial.println("Constants: pi, e");
Serial.println("Variables: x, y, z, u, v, w (e.g., 'x = 5*pi' then 'sin(x)')");
Serial.println("-------------------------------------");
Serial.print("Select mode - (r)adians or (d)egrees: ");
while (Serial.available() == 0) {}
char mode = Serial.read();
while (Serial.available() > 0) { Serial.read(); }
if (mode == 'd' || mode == 'D') {
isRadiansMode = false;
Serial.println("Degree Mode selected.");
} else {
isRadiansMode = true;
Serial.println("Radian Mode selected.");
}
Serial.println("-------------------------------------");
}
void loop() {
Serial.print("\nEnter Expression or Assignment (e.g., x = 5): ");
while (Serial.available() == 0) {}
String input = Serial.readStringUntil('\n');
input.trim();
if (input.length() > 0) {
int equalsPos = input.indexOf('=');
// --- NEW: Handle variable assignment ---
if (equalsPos != -1) {
String varStr = input.substring(0, equalsPos);
varStr.trim();
if (varStr.length() == 1 && isVariable(varStr.charAt(0))) {
char varChar = varStr.charAt(0);
int varIndex = getVariableIndex(varChar);
String expression = input.substring(equalsPos + 1);
expression.trim();
String postfix = calculator.toPostfix(expression);
double result = calculator.evaluatePostfix(postfix, isRadiansMode);
if (isnan(result)) {
Serial.println("Error: Cannot assign result of an invalid calculation.");
} else {
variableValues[varIndex] = result;
Serial.print("Set ");
Serial.print(varChar);
Serial.print(" = ");
Serial.println(result, 15);
}
} else {
Serial.println("Error: Invalid variable for assignment. Use x, y, z, u, v, or w.");
}
}
// --- Existing logic for direct evaluation ---
else {
Serial.print("Infix: ");
Serial.println(input);
String postfixExpression = calculator.toPostfix(input);
Serial.print("Postfix: ");
Serial.println(postfixExpression);
double result = calculator.evaluatePostfix(postfixExpression, isRadiansMode);
Serial.print("Result: ");
if (isnan(result)) {
Serial.println("Mathematical Error");
} else {
Serial.println(result, 15);
}
}
Serial.println("-------------------------------------");
}
}