Skip to content

Commit c8d4156

Browse files
committed
RPN v2.0.0 — symbolic stack
Evolve the stack value from a plain number into a closed-form symbolic value (num/den)·√rad·πⁿ: - Exact fractions, k·√m and rational multiples of π are preserved and simplified (8 √ 2 × → 4√2, π 2 ÷ → π/2, √2·√2 → 2, 1/√2 → √2/2). - Anything outside that form (√2+√3, sin, ln, overflow) falls back to a decimal approximation, so a result is never wrongly exact. - All arithmetic uses overflow-checked int64; π is an exact constant. - Factorial uses gamma(n+1) for large / non-integer arguments (no more unbounded loop). - Host tests extended to cover the symbolic rules and factorial edges. - Idempotent release workflow; CHANGELOG; README, landing page and roadmap updated.
1 parent 9beaf18 commit c8d4156

10 files changed

Lines changed: 367 additions & 220 deletions

File tree

.github/workflows/release.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,21 @@ jobs:
3131
- name: Stage release asset
3232
run: cp output/rpn.nwa rpn-${{ github.ref_name }}.nwa
3333

34-
- name: Create GitHub Release
34+
- name: Publish GitHub Release (idempotent)
3535
env:
3636
GH_TOKEN: ${{ github.token }}
37+
TAG: ${{ github.ref_name }}
3738
run: |
38-
gh release create "${{ github.ref_name }}" \
39-
"rpn-${{ github.ref_name }}.nwa#RPN app (${{ github.ref_name }}) — install via my.numworks.com/apps" \
40-
--title "RPN ${{ github.ref_name }}" \
41-
--notes "RPN calculator for NumWorks.
39+
FILE="rpn-${TAG}.nwa"
40+
if gh release view "$TAG" >/dev/null 2>&1; then
41+
echo "Release $TAG exists — updating asset."
42+
gh release upload "$TAG" "$FILE" --clobber
43+
else
44+
gh release create "$TAG" "${FILE}#RPN app (${TAG}) — install via my.numworks.com/apps" \
45+
--title "RPN ${TAG}" \
46+
--notes "RPN calculator for NumWorks.
4247
43-
**Install:** download \`rpn-${{ github.ref_name }}.nwa\` below, then open <https://my.numworks.com/apps>, plug in your calculator and upload the file.
48+
**Install:** download \`rpn-${TAG}.nwa\` below, then open <https://my.numworks.com/apps>, plug in your calculator and upload the file.
4449
4550
See the [project page](https://1e1.github.io/numworks-RPN/) for the full key map."
51+
fi

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here. This project follows
4+
[Semantic Versioning](https://semver.org/).
5+
6+
## v2.0.0 — Symbolic stack
7+
8+
- The stack value is now symbolic: exact results stay in the closed form
9+
`(num/den)·√rad·πⁿ`.
10+
- `8 √ 2 ×``4√2`, `π 2 ÷``π/2`, `√2·√2``2`, `1/√2``√2/2`.
11+
- Anything outside that closed form (`√2 + √3`, `sin`, `ln`, overflow) falls
12+
back to a decimal approximation — a result is never *wrongly* exact.
13+
- π is an exact constant; `→Dec` (`Ans`) forces the decimal form.
14+
- Factorial now uses `gamma(n+1)` for large or non-integer arguments instead of
15+
an unbounded loop.
16+
- All arithmetic remains overflow-checked `int64` with a decimal fallback.
17+
18+
## v1.0.0 — First release
19+
20+
- Reverse Polish Notation calculator as a NumWorks external app (`.nwa`).
21+
- Exact rational arithmetic for `+ − × ÷` and integer powers; IEEE double for
22+
transcendental functions.
23+
- Stack-oriented key map: operator keys act on the stack, RPN-unused keys become
24+
stack operations, `Toolbox` opens a stack menu; RAD/DEG angle mode.
25+
- Pure, host-tested numeric core (`make test`); device build via `nwlink` and
26+
`arm-none-eabi`.
27+
- GitHub Actions for CI, tagged releases and a GitHub Pages site with an
28+
interactive N0120 key map.

README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ A **Reverse Polish Notation** (RPN) calculator as an external app for the
44
[NumWorks](https://www.numworks.com) graphing calculator.
55

66
No parentheses, no `=`: you push numbers onto a stack and operators act on it.
7-
Arithmetic keeps **exact fractions** (`1/3 + 1/6` stays `1/2`), and functions
8-
fall back to decimal approximations — the familiar NumWorks feel, RPN-style.
7+
Results stay **symbolic and exact** where they can — fractions (`1/3 + 1/6`
8+
`1/2`), radicals (`8 √ 2 ×``4√2`), and rational multiples of π (`π 2 ÷`
9+
`π/2`) — and fall back to a decimal approximation otherwise. The familiar
10+
NumWorks feel, RPN-style.
911

1012
➡️ **Project page & key map:** <https://1e1.github.io/numworks-RPN/>
1113

1214
> **Scope.** NumWorks external apps run in a sandbox and cannot call the
13-
> built-in Poincaré engine, so this app ships its own numeric core: exact
14-
> rational arithmetic for `+ − × ÷` and integer powers, IEEE double for
15-
> transcendental functions. Full symbolic simplification would require a native
16-
> build of Epsilon rather than an external app.
15+
> built-in Poincaré engine, so this app ships its own symbolic core. Values are
16+
> held in the closed form `(num/den)·√rad·πⁿ`, which keeps exact fractions,
17+
> `k√m`, and rational multiples of π; anything outside that form (e.g. `√2 + √3`,
18+
> `sin`, `ln`) collapses to a decimal approximation — so a result is never
19+
> *wrongly* exact. A full expression-tree CAS with 2D layout would require a
20+
> native build of Epsilon rather than an external app (see the roadmap).
1721
1822
## Install
1923

@@ -31,12 +35,15 @@ stack. Subtraction and division are `level2 (op) level1`:
3135

3236
```
3337
3 EXE 6 ÷ → 1/2 exact fraction
34-
2 EXE √ → 1.414214 approximation
38+
2 EXE √ → √2 exact radical
39+
8 EXE √ 2 × → 4√2 simplified
40+
π EXE 2 ÷ → π/2 rational multiple of π
3541
5 EXE 4 EXE − → 1 (5 − 4)
36-
π EXE 2 × → 6.283185
3742
5 ! → 120 exact factorial
3843
```
3944

45+
Press `Ans` (→Dec) to force the decimal approximation of the top level.
46+
4047
If you enter operands in the wrong order, press **( = SWAP** to exchange the top
4148
two levels.
4249

docs/ROADMAP.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
# Roadmap — v2: symbolic stack with Poincaré-style presentation
1+
# Roadmap — symbolic stack with Poincaré-style presentation
22

3-
v1.0.0 is a numeric RPN calculator: the stack holds *numbers* (exact rationals
4-
or doubles) and every operator computes eagerly. v2 changes the nature of the
5-
stack.
3+
> **Status.** v1.0.0 held plain numbers (exact rationals or doubles). **v2.0.0
4+
> ships the first symbolic layer**: values now live in the closed form
5+
> `(num/den)·√rad·πⁿ`, so `8 √ 2 ×` gives `4√2`, `π 2 ÷` gives `π/2`, and
6+
> `√2·√2` gives `2` — exact where possible, decimal otherwise. The steps below
7+
> describe what remains to reach a *full* expression-tree CAS with 2D layout.
68
79
## Vision
810

docs/index.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ <h3>Stack, not lines</h3>
123123
<p>Push numbers and let operators act on them. Fast, keystroke-efficient, and ideal for long chained calculations.</p>
124124
</div>
125125
<div class="card">
126-
<span class="emoji">½</span>
127-
<h3>Exact fractions</h3>
128-
<p><code>1/3 + 1/6</code> stays <code>1/2</code>. Arithmetic and integer powers keep exact rationals; functions give decimals.</p>
126+
<span class="emoji"></span>
127+
<h3>Exact &amp; symbolic</h3>
128+
<p>Fractions, radicals and multiples of π stay exact — <code>1/2</code>, <code>4√2</code>, <code>π/2</code> — with a decimal fallback when they can't.</p>
129129
</div>
130130
<div class="card">
131131
<span class="emoji">⌨️</span>
@@ -137,11 +137,11 @@ <h3>Familiar keys</h3>
137137

138138
<section>
139139
<h2>See it in action</h2>
140-
<pre>3 <b>EXE</b> 6 ÷ <span class="out">→ 1/2</span> exact fraction
141-
2 <b>EXE</b><span class="out">1.414214</span> approximation
142-
5 <b>EXE</b> 4 <b>EXE</b> <span class="out">1</span> (5 − 4)
143-
π <b>EXE</b> 2 × <span class="out">6.283185</span>
144-
5 ! <span class="out">→ 120</span> exact factorial</pre>
140+
<pre>3 <b>EXE</b> 6 ÷ <span class="out">→ 1/2</span> exact fraction
141+
2 <b>EXE</b><span class="out">√2</span> exact radical
142+
8 <b>EXE</b> √ 2 × <span class="out">4√2</span> simplified
143+
π <b>EXE</b> 2 ÷ <span class="out">π/2</span> rational multiple of π
144+
5 ! <span class="out">→ 120</span> exact factorial</pre>
145145
<p class="note">Subtraction and division are <em>level 2 (op) level 1</em>. Entered the operands in the wrong order? Press <kbd>(</kbd> to <strong>SWAP</strong> the top two.</p>
146146
</section>
147147

src/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const uint32_t eadk_api_level
2424
using EADK::Keyboard::Event;
2525

2626
int main(int argc, char* argv[]) {
27+
(void)argc;
28+
(void)argv;
29+
2730
Engine engine;
2831
View view;
2932

src/rpn.cpp

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ constexpr double kE = 2.71828182845904523536;
1111
double toRadians(double x) { return x * kPi / 180.0; }
1212
double toDegrees(double x) { return x * 180.0 / kPi; }
1313

14-
double factorialDouble(double x) {
15-
// Only defined here for non-negative integers; caller validates.
16-
double result = 1.0;
17-
for (int i = 2; i <= (int)(x + 0.5); i++) result *= i;
18-
return result;
19-
}
14+
// Largest n whose factorial fits exactly in int64 (20! < 2^63 <= 21!).
15+
constexpr int64_t kMaxExactFactorial = 20;
2016
} // namespace
2117

2218
void Engine::toggleAngleMode() {
@@ -126,7 +122,7 @@ void Engine::execute(Command command) {
126122
if (!m_input.isEmpty()) {
127123
Value v = Value::parse(m_input.text());
128124
m_input.clear();
129-
if (v.isExact()) level = (int)v.numerator();
125+
if (v.isInteger()) level = (int)v.integerValue();
130126
}
131127
if (!m_stack.pick(level)) setStatus("Bad level");
132128
break;
@@ -206,23 +202,23 @@ void Engine::execute(Command command) {
206202
if (!ensureOperands(1)) break;
207203
Value a = m_stack.pop();
208204
double x = a.toDouble();
209-
if (a.isExact() && a.denominator() == 1 && a.numerator() >= 0 &&
210-
a.numerator() <= 20) {
205+
if (x < 0.0) {
206+
m_stack.push(Value::undefined());
207+
setStatus("Factorial needs n>=0");
208+
} else if (a.isInteger() && a.integerValue() <= kMaxExactFactorial) {
211209
int64_t r = 1;
212-
for (int64_t i = 2; i <= a.numerator(); i++) r *= i;
210+
for (int64_t i = 2; i <= a.integerValue(); i++) r *= i;
213211
m_stack.push(Value::integer(r));
214-
} else if (x >= 0.0) {
215-
m_stack.push(Value::real(factorialDouble(x)));
216212
} else {
217-
m_stack.push(Value::undefined());
218-
setStatus("Factorial needs n>=0");
213+
// Larger or non-integer arguments: n! = gamma(n+1) as a double.
214+
m_stack.push(Value::real(tgamma(x + 1.0)));
219215
}
220216
break;
221217
}
222218

223219
case Command::PushPi:
224220
commitInput();
225-
m_stack.push(Value::real(kPi));
221+
m_stack.push(Value::pi());
226222
break;
227223
case Command::PushE:
228224
commitInput();

0 commit comments

Comments
 (0)