summaryrefslogtreecommitdiff
path: root/gemfeed/2026-06-01-gt-calculator.gmi
blob: c361712afae1abab07b24e835c21afb628ab0a19 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# `gt` calculator - a calculator built with local LLMs

> Published at 2026-05-31T14:24:10+03:00

I created a calculator. Not because the world needed another one, but because I wanted to test something: how well do local LLMs hold up as pair programmers on a real project?

The answer is: well enough.

`gt` is a command-line calculator written in Go that does RPN (Reverse Polish Notation), percentage calculations, unit conversion, and a fair bit more. The name stands for "greater than" — `gt` is a comparison operator the calculator supports. Plus it was free in my terminal and I liked the short name.

If you want the full feature guide, the README links to a detailed doc for every feature covered here and more:

=> https://codeberg.org/snonux/gt gt on Codeberg
=> ./gt-calculator/logo.svg gt logo

The whole thing — code, tests, documentation, even the logo — was built using only LLMs that can run locally on reasonable hardware: Qwen, Gemma, Nemotron, GPT-OSS. To be honest, I didn't run them locally either — I rented a Hyperstack VMs with NVidia GPU just to get a feel for the quality before investing in hardware. The point was to test models that don't require a cloud API and could realistically run on your own box.

=> https://www.hyperstack.cloud/

This post is about the calculator and what it does. My experience running those LLMs as pair programmers will be a separate post later.

And no, this wasn't vibe-coded. I used a specific technique and a set of AI skills to drive the LLMs. The codebase came out well-structured and maintainable — not the "prompt it and pray" mess. 

## Table of Contents

* ⇢ `gt` calculator - a calculator built with local LLMs
* ⇢ ⇢ The motivation
* ⇢ ⇢ What it does
* ⇢ ⇢ Percentage calculations
* ⇢ ⇢ RPN arithmetic
* ⇢ ⇢ ⇢ Logarithms
* ⇢ ⇢ ⇢ Hyper operators (n-ary)
* ⇢ ⇢ ⇢ Comparisons and booleans
* ⇢ ⇢ Variables and symbols
* ⇢ ⇢ Built-in constants
* ⇢ ⇢ The metrics system
* ⇢ ⇢ ⇢ Suffix notation
* ⇢ ⇢ ⇢ Unit conversion
* ⇢ ⇢ ⇢ Metric-aware arithmetic
* ⇢ ⇢ ⇢ Custom metrics
* ⇢ ⇢ ⇢ SI vs IEC modes
* ⇢ ⇢ Stack manipulation
* ⇢ ⇢ Rational number mode
* ⇢ ⇢ The REPL
* ⇢ ⇢ Some more usage examples
* ⇢ ⇢ Fish shell completions
* ⇢ ⇢ Installation
* ⇢ ⇢ Wrapping up

## The motivation

Four things drove this.

First, I wanted to test local LLMs as coding partners. Not the cloud-hosted ones with infinite context and billions of parameters — the ones you can actually run yourself. I figured a calculator project is big enough to be interesting but small enough to finish.

Second, cloud independence is a thing I care about. I build tools that don't need a network connection to function. Writing software that talks to OpenAI or Anthropic APIs doesn't count as "running locally." Everything here runs offline.

Third, I wanted to learn more about how these models actually work in practice. Not benchmarks or leaderboard scores — the day-to-day experience. How do you operate them? How do you structure prompts? When do they produce clean code versus garbage? Where do they struggle?

Finally, the tool needed to be genuinely useful. A toy that calculates `2 + 2` isn't worth the disk space. I aimed for a calculator I'd actually reach for.

## What it does

At its core, `gt` is a stack-based RPN calculator with percentage support and a full metrics system. It runs as a single binary, has no dependencies, and works three ways:

```sh
gt '3 4 +'                     # one-liner: RPN
gt '20% of 150'                # one-liner: percentage
gt                             # interactive REPL
```

You can also pipe into it: `echo '1000Mbps @Gbps convert' | gt` → `1`.

## Percentage calculations

This is the simplest entry point. Three forms, all case-insensitive, all with step-by-step output:

```sh
gt '20% of 150'                # → 30.00
gt '30 is what % of 150'       # → 20.00%
gt '30 is 20% of what'         # → 150.00
```

Every percentage result shows the formula and intermediate values, so you can verify the math. Useful for tips, discounts, tax, and any "what's the actual number?" moment.

## RPN arithmetic

The main engine uses Reverse Polish Notation. No parentheses needed — the order of tokens determines the order of operations.

```sh
gt '3 4 +'                     # 7
gt '2 10 ^'                    # 1024
gt '100 10 / 5 +'              # 15
gt '3 4 + 5 6 + *'             # (3+4) × (5+6) = 77
```

Six basic operators: `+`, `-`, `*`, `/`, `^`, `%`. All work on the stack, popping operands and pushing the result.

The fast integer power operator `**` uses binary exponentiation (O(log n) instead of O(n)). So `2 100 **` does about 7 multiplications instead of 99. It only accepts integer exponents — use `^` for fractional powers.

### Logarithms

Three unary operators for when you need them:

- `lg` — base 2 (information theory, algorithm complexity)
- `log` — base 10 (decibels, pH, order of magnitude)
- `ln` — natural log (continuous growth, statistics)

```sh
gt '1024 lg'                   # → 10
gt '1000 log'                  # → 3
gt 'e ln'                      # → 1.0000000000
```

### Hyper operators (n-ary)

Want to add everything on the stack at once? Square-bracket operators pop the entire stack and reduce left-associatively:

```sh
gt '1 2 3 4 5 [+]'            # → 15 (sum of all)
gt '100 10 20 30 5 [-]'       # → 35 (100-10-20-30-5)
gt '2 5 10 [*]'               # → 100 (product)
gt '1000 2 2 2 2 [/]'         # → 62.5
```

Full set: `[+]`, `[-]`, `[*]`, `[/]`, `[^]`, `[%]` for arithmetic, plus `[lg]`, `[log]`, `[ln]` for logarithms. The log hyper operators work differently from the arithmetic ones — they compute the sum of the log function applied to each value, not a left-associative reduction. The square-bracket syntax is inspired by Raku's hyper operators.

=> https://raku.org 

### Comparisons and booleans

Six comparison operators, each with a symbolic alias:

```sh
gt '5 3 gt'                    # → true
gt '3 5 <'                     # → true
gt '5 5 =='                    # → true
```

Results are `true` or `false`, which coerce into arithmetic (true = 1, false = 0). That means you can do inline conditionals:

```sh
gt '85 80 gt 10 *'             # → 10 (85 > 80, so 1 × 10)
gt '50 80 gt 10 *'             # → 0  (50 < 80, so 0 × 10)
```

Range validation works by summing boolean results:

```sh
gt '72 68 gte 100 lte +'       # → 2 (both checks pass, temp is in range)
gt '105 68 gte 100 lte +'      # → 1 (out of range)
```

## Variables and symbols

Store values with three assignment styles:

```sh
gt 'x 10 :='                   # right assignment
gt '20 y =:'                   # left assignment
gt 'rate 100Mbps ='            # standard (or rate = 100Mbps)
```

`vars` lists them, `clear` wipes all variables and constants, `:name d` deletes one. In REPL mode, variables persist to disk between sessions.

Symbols (the `:x` syntax) are named placeholders on the stack. They're how you do explicit variable assignment and deletion without ambiguity. Bare identifiers that don't match any variable or constant also push as symbols.

## Built-in constants

Thirty-six of them. Use them directly as tokens:

```sh
gt 'pi 2 *'                    # → 6.283185307
gt 'euler'                     # → 2.718281828
gt 'phi 10 *'                  # → 16.18 (golden rectangle)
gt 'sqrt2 sqrt3 *'             # → 2.449 (√6)
```

Greek letter aliases work too: `π`, `τ`, `φ`, `√2`, `√3`, `√5`.

## The metrics system

This is where `gt` earns its swiss army knife title. Every number carries a unit of measurement, and arithmetic understands those units.

Six built-in categories, plus `Cool` — the default unitless metric for plain numbers. The name comes from Raku's `Cool` role, which represents things that are "cool enough" to do basic operations (strings, numbers, etc.). In `gt`, Cool values absorb into any metric category during arithmetic, so `5 100Mbps +` treats the `5` as `5Mbps`.

- *DataRate*: bps, Kbps, Mbps, Gbps, Tbps
- *DataSize*: bits, bytes, KB/MB/GB/TB/PB (SI), KiB/MiB/GiB/TiB/PiB (IEC)
- *Time*: ms, s, min, hr, day
- *Weight*: mg, g, kg, lb, oz, ton
- *Speed*: mps, kmh, mph, knots
- *Distance*: m, km, mi, ft, in, nm (nautical miles)

### Suffix notation

Attach units directly to numbers:

```sh
gt '100Mbps'                   # 100 megabits per second
gt '5GB'                       # 5 gigabytes
gt '1hr'                       # 1 hour
gt '70kg'                      # 70 kilograms
```

### Unit conversion

Use `@<target> convert`:

```sh
gt '1000Mbps @Gbps convert'    # → 1
gt '1km @mi convert'           # → 0.6213711922
gt '60mph @kmh convert'        # → 96.56
gt '3day @s convert'           # → 259200
```

### Metric-aware arithmetic

Addition and subtraction auto-convert within categories:

```sh
gt '1km 500m +'                # → 1.5 (converted to km)
gt '1Gbps 500Mbps -'           # → 500 (in Mbps)
```

Multiplication and division do cross-category inference:

```sh
gt '100Mbps 1hr *'             # rate × time = data transferred
gt '10GB 2hr /'                # data / time = rate
gt '100kmh 1hr * @mi convert'  # → 62.14 miles traveled
```

Comparison operators are metric-aware too:

```sh
gt '1km 1000m eq'              # → true
gt '1GB 1024MB eq'             # → false (SI: 1GB = 1000MB)
```

### Custom metrics

Define your own units:

```sh
custom define reel 304.8 Distance    # surveyor's reel
custom define fortnight 1209600 Time
```

Then use them like built-ins: `5reel @m convert` → 1524.

### SI vs IEC modes

Data size units have two modes. SI (default) uses powers of 1000. IEC uses powers of 1024. Switch with `metric decimal set` / `metric binary set`. The dedicated IEC units (KiB, MiB, GiB) are always unambiguous.

## Stack manipulation

Five operators for managing the RPN stack:

- `dup` — duplicate the top value
- `swap` — swap top two values
- `pop` — discard the top value
- `show` / `showstack` / `print` — display the stack without modifying it
- `clear` — clear all variables and constants

`dup` and `swap` come up a lot. Square a number: `7 dup *` → 49. Reverse operand order: `2 10 swap /` → 5 (instead of 0.2).

## Rational number mode

"Rational" refers to rational numbers — numbers that can be expressed as an exact fraction of two integers (numerator/denominator). The name "rat" is just the shorthand command.

In the default float64 mode, numbers are stored as binary floating-point approximations. `0.1` cannot be represented exactly in binary, so it becomes something like `0.1000000000000000055511151231257827...`. This causes the classic problem:

```sh
> rat off
Rational mode disabled (using float64)
> 0.3 0.1 - 0.2 -
-2.775557562e-17
```

The result should be `0`, but floating-point rounding errors accumulated. Silent wrong answer.

Rational mode stores numbers as exact fractions using Go's `math/big.Rat`. `0.1` is stored as `1/10`, `0.2` as `2/10` (simplified to `1/5`), and all arithmetic operates on those exact values. No binary approximation, no silent drift.

```sh
> rat on
Rational mode enabled
> 0.1 0.2 +
0.3000000000
```

Internally, `1/3` stays as the exact fraction `1/3`, and `1/3 * 3` computes to exactly `3/3 = 1`. No binary conversion, no rounding.

REPL-only. Has a known limitation with non-dyadic decimals and metric operations — the docs explain the why.

## The REPL

Run `gt` with no arguments when attached to a terminal and you get the interactive session. Command history (1000 entries, persisted to `~/.gt_history`), tab completion, Emacs-style line editing, Ctrl+R reverse search.

Variables save to disk between sessions. Session logging with `--log session.log`. Session state lives in `~/.local/state/gt/vars`.

Built-in REPL commands: `help`, `clear`, `quit`/`exit`, `rpn`/`calc`, `rat`, `stack`. Tab-completes.

Here's what a session looks like:

```sh
$ gt
> rate 100Mbps =
rate = 100
> time 2hr =
time = 2
> rate time *
200 Mbps
> metric show
Mbps, DataRate, base: bps, factor: 1e+06
> download 50GB =
download = 50
> speed 100Mbps =
speed = 100
> download speed / @min convert
83.33333333
> custom define reel 304.8 Distance
defined custom metric "reel" (factor: 304.8, category: Distance)
> 5reel @m convert
1524
> 100Mbps 50Mbps swap -
50 Mbps
> show
50 Mbps
> 20% of 150
20.00% of 150.00 = 30.00
  Steps: (20.00 / 100) * 150.00 = 0.20 * 150.00 = 30.00
> rat on
Rational mode enabled
> 1 3 / 3 *
1.0000000000
> vars
rate = 100
time = 2
download = 50
speed = 100
```

Inline help is built in. Bare `help` lists all commands, and `help <command>` drills into one topic:

```sh
> help rat
rat on/off/toggle - Switch between float64 and rational number modes
  rat on       Enable rational mode (exact fractions)
  rat off      Disable rational mode (use float64)
  rat toggle   Toggle current mode
> help clear
clear - Clear the screen
Usage: clear
```

No need to leave the REPL or dig through docs when you forget a subcommand.

## Some more usage examples

Here are some more `gt` usage examples:

```sh
# Download volume at 1 Gbps for an hour
gt '1Gbps 1hr * @GB convert'        # → 450

# Internet speed threshold in scripts
MIN_SPEED=$(gt '1Gbps @Mbps convert')  # → 1000

# Travel planning
gt '500mi @km convert'              # → 804.67
gt '65mph @kmh convert'             # → 104.86

# Tips, discounts, whatever
gt '18% of 63.40'
gt '15% of 89.99'

# File sizes in MiB instead of bytes
find . -exec wc -c {} + | awk '{print $1}' | xargs -I{} gt '{} @MiB convert'

# Quick math
gt 'pi 5 5 * *'                     # circle area, r=5 → 78.54
gt '1000 lg'                        # → 10 (bits needed)
```

## Fish shell completions

`gt` ships with a fish completion script that covers everything: operators, constants, metric units, stack commands, the `metric` and `custom` subcommand trees, and boolean literals. It's context-aware — it won't suggest `metric` subcommands in the middle of an RPN expression, and it suppresses fish's default file completions so you only see calculator tokens.

Install it:

```sh
cp completions/gt.fish ~/.config/fish/completions/
```

Or system-wide:

```sh
sudo cp completions/gt.fish /usr/local/share/fish/vendor_completions.d/
```

In practice this means tab-completion for all 36 constants, every metric unit (bps through Tbps, KB through PiB, kmh, mph, knots, etc.), all arithmetic and hyper operators, and the `metric show` / `metric list` / `custom define` subcommand chains. The `custom define` subcommand even completes the valid category names so you don't have to memorize them.

## Installation

```sh
go install codeberg.org/snonux/gt/cmd/gt@latest
```

Or from the source directory: `mage install`.

## Wrapping up

The local LLM experiment worked. The code is clean enough, the tests pass, the docs are thorough, and I use the tool. The logo was generated by a local model too.

For the complete and always-up-to-date feature guide, detailed docs for every feature, and the source code, head to the repo.

=> https://codeberg.org/snonux/gt gt on Codeberg

But will I now invest a couple of thousand dollars in hardware to run Qwen 2.5 35B or 27B? (I used the dense 27B model most of the time to build `gt`). Unfortunately, no. I don't think it's worth the cost yet, as cloud models are still cheaper and more convenient.

However, I will keep an eye on how the technology develops and continue experimenting with rented Hyperstack VMs for now; I will also default more often to smaller LLMs that could potentially run on home hardware. Ollama Cloud subscription or an OpenRouter API key are also good options alongside Claude and OpenAI Codex.

I will write another blog post at some point about my setup and what I learned from self-hosting models on Hyperstack.

Other related posts:

=> ./2026-06-01-gt-calculator.gmi 2026-06-01 `gt` calculator - a calculator built with local LLMs (You are currently reading this)
=> ./2025-08-05-local-coding-llm-with-ollama.gmi 2025-08-05 Local LLM for Coding with Ollama on macOS

E-Mail your comments to `paul@nospam.buetow.org` :-)

=> ../ Back to the main site