Introduction
Do you remember COBOL, this ancient dinosaur which had been the number One language for a very long time? Ok, Fortran as a scientific programming language was also widely used.
Here’s a complete, portable COBOL program (works nicely with GnuCOBOL) that lets the user pick a calculation (VAT from net, VAT from gross, compound interest, simple interest), then prompts for the required values and prints the results.
IDENTIFICATION DIVISION.
PROGRAM-ID. FINANCE-CALC.
AUTHOR. YOU.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
*> If you prefer comma decimals (e.g., 19,5), uncomment next line:
*> DECIMAL-POINT IS COMMA.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MENU-CHOICE PIC 9 VALUE 0.
01 WS-CONTINUE PIC X VALUE 'Y'.
*> Common fields
01 WS-RATE-PCT PIC 9(5)V9(4) VALUE 0. *> e.g., 19.0 for 19%
01 WS-RATE PIC 9(2)V9(8) VALUE 0. *> decimal, e.g., 0.19
01 WS-NET PIC 9(12)V9(4) VALUE 0.
01 WS-GROSS PIC 9(12)V9(4) VALUE 0.
01 WS-VAT PIC 9(12)V9(4) VALUE 0.
*> Compound/Simple interest fields
01 WS-PRINCIPAL PIC 9(12)V9(4) VALUE 0.
01 WS-YEARS PIC 9(4)V9(4) VALUE 0.
01 WS-COMPOUNDS-PER-YEAR PIC 9(3) VALUE 0.
01 WS-PERIODS-TOTAL PIC 9(7) VALUE 0.
01 WS-PERIOD PIC 9(7) VALUE 0.
01 WS-PERIOD-RATE PIC 9(2)V9(8) VALUE 0.
01 WS-AMOUNT PIC 9(12)V9(4) VALUE 0.
01 WS-INTEREST PIC 9(12)V9(4) VALUE 0.
*> Display helpers (2 decimals for money)
01 DS-NET PIC Z(12)9.99.
01 DS-GROSS PIC Z(12)9.99.
01 DS-VAT PIC Z(12)9.99.
01 DS-AMOUNT PIC Z(12)9.99.
01 DS-INTEREST PIC Z(12)9.99.
01 DS-RATE-PCT PIC Z(5)9.9999.
PROCEDURE DIVISION.
MAIN-LOOP.
PERFORM UNTIL WS-CONTINUE NOT = 'Y'
PERFORM SHOW-MENU
PERFORM PROCESS-CHOICE
DISPLAY "Do another calculation? (Y/N): " WITH NO ADVANCING
ACCEPT WS-CONTINUE
MOVE FUNCTION UPPER-CASE(WS-CONTINUE) TO WS-CONTINUE
END-PERFORM
DISPLAY "Bye!"
STOP RUN.
SHOW-MENU.
DISPLAY "==============================================".
DISPLAY " Finance Calculator".
DISPLAY " 1) VAT from NET (NET -> VAT + GROSS)".
DISPLAY " 2) VAT from GROSS (GROSS -> VAT + NET)".
DISPLAY " 3) Compound Interest (principal grows over time)".
DISPLAY " 4) Simple Interest".
DISPLAY " 0) Exit".
DISPLAY "==============================================".
DISPLAY "Your choice (0-4): " WITH NO ADVANCING
ACCEPT WS-MENU-CHOICE.
PROCESS-CHOICE.
EVALUATE WS-MENU-CHOICE
WHEN 1
PERFORM VAT-FROM-NET
WHEN 2
PERFORM VAT-FROM-GROSS
WHEN 3
PERFORM COMPOUND-INTEREST
WHEN 4
PERFORM SIMPLE-INTEREST
WHEN 0
MOVE 'N' TO WS-CONTINUE
WHEN OTHER
DISPLAY "Invalid choice."
END-EVALUATE.
ASK-RATE.
DISPLAY "Enter rate in percent (e.g., 19 or 19.5): " WITH NO ADVANCING
ACCEPT WS-RATE-PCT
COMPUTE WS-RATE ROUNDED = WS-RATE-PCT / 100.
VAT-FROM-NET.
DISPLAY "Enter NET amount: " WITH NO ADVANCING
ACCEPT WS-NET
PERFORM ASK-RATE
COMPUTE WS-VAT ROUNDED = WS-NET * WS-RATE
COMPUTE WS-GROSS ROUNDED = WS-NET + WS-VAT
MOVE WS-NET TO DS-NET
MOVE WS-RATE-PCT TO DS-RATE-PCT
MOVE WS-VAT TO DS-VAT
MOVE WS-GROSS TO DS-GROSS
DISPLAY "NET: " DS-NET
DISPLAY "Rate: " DS-RATE-PCT " %"
DISPLAY "VAT: " DS-VAT
DISPLAY "GROSS: " DS-GROSS.
VAT-FROM-GROSS.
DISPLAY "Enter GROSS amount: " WITH NO ADVANCING
ACCEPT WS-GROSS
PERFORM ASK-RATE
*> VAT part = GROSS * (rate / (1 + rate)), NET = GROSS - VAT
COMPUTE WS-VAT ROUNDED = WS-GROSS * (WS-RATE / (1 + WS-RATE))
COMPUTE WS-NET ROUNDED = WS-GROSS - WS-VAT
MOVE WS-GROSS TO DS-GROSS
MOVE WS-RATE-PCT TO DS-RATE-PCT
MOVE WS-VAT TO DS-VAT
MOVE WS-NET TO DS-NET
DISPLAY "GROSS: " DS-GROSS
DISPLAY "Rate: " DS-RATE-PCT " %"
DISPLAY "VAT: " DS-VAT
DISPLAY "NET: " DS-NET.
COMPOUND-INTEREST.
DISPLAY "Enter principal (start amount): " WITH NO ADVANCING
ACCEPT WS-PRINCIPAL
PERFORM ASK-RATE
DISPLAY "Years (e.g., 5 or 5.5): " WITH NO ADVANCING
ACCEPT WS-YEARS
DISPLAY "Compounds per year (e.g., 1, 4, 12): " WITH NO ADVANCING
ACCEPT WS-COMPOUNDS-PER-YEAR
IF WS-COMPOUNDS-PER-YEAR = 0
DISPLAY "Compounds per year must be >= 1. Using 1."
MOVE 1 TO WS-COMPOUNDS-PER-YEAR
END-IF
COMPUTE WS-PERIODS-TOTAL ROUNDED =
FUNCTION INTEGER(WS-YEARS * WS-COMPOUNDS-PER-YEAR)
COMPUTE WS-PERIOD-RATE ROUNDED = WS-RATE / WS-COMPOUNDS-PER-YEAR
MOVE WS-PRINCIPAL TO WS-AMOUNT
*> Iterative multiplication to avoid EXP/LOG dependencies
MOVE 0 TO WS-PERIOD
PERFORM UNTIL WS-PERIOD >= WS-PERIODS-TOTAL
COMPUTE WS-AMOUNT ROUNDED = WS-AMOUNT * (1 + WS-PERIOD-RATE)
ADD 1 TO WS-PERIOD
END-PERFORM
COMPUTE WS-INTEREST ROUNDED = WS-AMOUNT - WS-PRINCIPAL
MOVE WS-AMOUNT TO DS-AMOUNT
MOVE WS-INTEREST TO DS-INTEREST
MOVE WS-RATE-PCT TO DS-RATE-PCT
DISPLAY "Principal: " WS-PRINCIPAL
DISPLAY "Rate (annual): " DS-RATE-PCT " %"
DISPLAY "Years: " WS-YEARS
DISPLAY "Compounds/year: " WS-COMPOUNDS-PER-YEAR
DISPLAY "Final amount: " DS-AMOUNT
DISPLAY "Total interest: " DS-INTEREST.
SIMPLE-INTEREST.
DISPLAY "Enter principal (start amount): " WITH NO ADVANCING
ACCEPT WS-PRINCIPAL
PERFORM ASK-RATE
DISPLAY "Years (e.g., 5 or 5.5): " WITH NO ADVANCING
ACCEPT WS-YEARS
COMPUTE WS-INTEREST ROUNDED = WS-PRINCIPAL * WS-RATE * WS-YEARS
COMPUTE WS-AMOUNT ROUNDED = WS-PRINCIPAL + WS-INTEREST
MOVE WS-INTEREST TO DS-INTEREST
MOVE WS-AMOUNT TO DS-AMOUNT
MOVE WS-RATE-PCT TO DS-RATE-PCT
DISPLAY "Principal: " WS-PRINCIPAL
DISPLAY "Rate (annual): " DS-RATE-PCT " %"
DISPLAY "Years: " WS-YEARS
DISPLAY "Final amount: " DS-AMOUNT
DISPLAY "Interest: " DS-INTEREST.
A fun thought experiment!
If “COBOL” were invented today for its original niche—business systems, finance, ledgers, batch + online processing—it would probably look like a memory-safe, strongly typed, declarative-first language with native money/time types, effect safety, and frictionless data/DB/stream integration. Think of the clarity of classic COBOL, the ergonomics of TypeScript/Kotlin, the decimal rigor of SQL, and the reliability features of Rust.
Here’s what such a “Modern COBOL” (let’s call it Cobol-Next) would likely include:
What it would prioritize
- Human-readable but compact syntax. Sentence-like keywords, but no shouting case or needless verbosity.
- First-class financial primitives. money<EUR>, rate%, decimal(38,4), rounding(mode=Bankers, scale=2), fx(EUR->USD, at=ECB).
- Time & calendars. date, timestamp, period, business_day(calendar=TARGET2), holiday calendars, day_count(Actual_360, 30E_360).
- Schema-aware data. Native table/record types; transparent I/O for CSV/Parquet/JSON/Avro; schema evolution baked in.
- SQL & streams built in. select/group by inline, plus from stream payments window 15m tumble ....
- Deterministic decimal math. No binary float surprises; explicit rounding; overflow checks by default.
- Effect & error safety. Result<T,E>, try, defer, capability-scoped I/O (@effects(db, net)), transactional blocks.
- Concurrency for business. Async I/O, actors for services, sagas/workflows for distributed transactions.
- Compliance hooks. Audit logs, immutability options, PII masking, consent policies, deterministic replay for audits.
- Interop. Direct SQL, gRPC/HTTP, WASM for sandboxing rules, JVM/.NET/FFI bindings.
- Tooling. Package manager, codegen for APIs/DBs, migration tool, linter, formatter, test & property-based testing, profiler.
- Deployment. First-class batch, cron, and service targets; containers; observability (OpenTelemetry).
Let me give you a tiny taste of the syntax
1) Module, types, and invariants
module finance.calculations
use time.{date, period}
use money.{money, rate%, rounding}
use data.{table, from_csv, to_csv}
use sql
use effects(db, fs)
type VatRate = rate% // e.g., 19%, 7%
record VatBreakdown {
net : money<EUR>
vat : money<EUR>
gross : money<EUR>
} ensure gross == net + vat
fn vat_from_net(net: money<EUR>, rate: VatRate): VatBreakdown {
let vat = net * rate with rounding(scale=2, mode=Bankers)
let gross = net + vat
return { net, vat, gross }
}
fn vat_from_gross(gross: money<EUR>, rate: VatRate): VatBreakdown {
let vat = gross * (rate / (1% + rate)) with rounding(scale=2)
let net = gross - vat
return { net, vat, gross }
}
2) Interest (simple & compound) with precise decimals
fn simple_interest(p: money<EUR>, annual: rate%, years: decimal(10,4))
-> { amount: money<EUR>, interest: money<EUR> } {
let interest = p * annual * years with rounding(scale=2)
return { amount: p + interest, interest }
}
fn compound_interest(
p: money<EUR>, annual: rate%, years: decimal(10,4), n: u32 // compounds/year
) -> { amount: money<EUR>, interest: money<EUR> } {
require n >= 1
let r = annual / n%
// Deterministic loop (avoids platform EXP/LOG diffs)
var a = p
repeat (years * n).floor() times { a = a * (1 + r) with rounding(scale=8) }
let interest = (a - p) with rounding(scale=2)
return { amount: (p + interest), interest }
}
3) Inline SQL over a declared schema (typed!)
table invoices(id u64, customer text, net money<EUR>, rate VatRate, issued date);
service reporting @effects(db) {
// Typed query; DB driver generated from schema
fn monthly_vat(yyyy_mm: text) -> table(customer text, vat money<EUR>) {
return sql """
select customer, sum(net * (rate))::money<EUR> as vat
from invoices
where to_char(issued, 'YYYY-MM') = :yyyy_mm
group by customer
""";
}
}
4) Streams & sagas (for real systems)
stream payments(topic="payments", key=customer_id)
: record { customer_id u64, amount money<EUR>, ts timestamp };
actor settlement {
state total_by_customer : map<u64, money<EUR>> = {};
on payments as p {
total_by_customer[p.customer_id] += p.amount with rounding(scale=2);
if end_of_day() then persist_snapshot();
}
}
saga close_month @effects(db) {
step "lock-books" { sql "select lock_period(:month)"; }
step "post-entries" compensates "post-entries-reverse" {
// ... post GL entries
}
step "unlock" { sql "select unlock_period(:month)"; }
}
5) A tiny CLI that does the earlier calculator menu—concise, safe, testable
cli finance-calc {
cmd vat-from-net (net: money<EUR>, rate: VatRate) {
print vat_from_net(net, rate)
}
cmd vat-from-gross (gross: money<EUR>, rate: VatRate) {
print vat_from_gross(gross, rate)
}
cmd simple-interest (p: money<EUR>, rate: rate%, years: decimal(10,4)) {
print simple_interest(p, rate, years)
}
cmd compound-interest (p: money<EUR>, rate: rate%, years: decimal(10,4), n: u32=12) {
print compound_interest(p, rate, years, n)
}
}
Design choices (why)
- Business-grade numeric integrity. Decimal everywhere, explicit rounding, deterministic math = no audit fights.
- English-leaning names, modern structure. Readable to domain experts, compact enough for engineers.
- Typed data + SQL + streams. Most business code is data plumbing, validation, and reporting—make it first-class.
- Effect typing & sagas. Production systems live in the messy world of I/O and partial failure—make it explicit and safe.
- Actors for services; batch is a citizen. COBOL did batch brilliantly; today we want batch + services + streams seamlessly.
- Regulatory features. Built-in audit trails, PII policies, determinism switches, and replayability for compliance.
Conclusions
The fictive Cobol-Next project demonstrates how a decades-old paradigm — descriptive business logic expressed in near-natural language — can be reimagined for the 21st century without sacrificing clarity or safety.
- Readable and Declarative by Design
Cobol-Next retains COBOL’s original virtue: self-explanatory code. Yet it replaces the verbose “English-like” syntax with a concise, statically typed language resembling modern data-centric DSLs. Concepts such as record, money<Currency>, rate%, and decimal make financial semantics explicit while remaining human-readable. - Strong Type Safety and Determinism
Built-in monetary and percentage types eliminate rounding errors common in legacy COBOL or spreadsheet systems. Deterministic, banker’s-rounding arithmetic and currency consistency checking ensure correctness by construction — crucial for finance, accounting, and risk analytics. - Interoperability and Portability
The compiler may emit C99 or LLVM IR, producing native binaries that can integrate easily with existing ecosystems. This dual-backend design makes Cobol-Next suitable both for low-level embedded deployments and for cloud services compiled to WASM or LLVM targets. - Structured Concurrency and Future-Proofing
By treating each fn and record as first-class citizens and preparing for actor-style service constructs, Cobol-Next bridges the gap between business transaction logic and modern asynchronous systems. It is future-ready for integration with message buses, databases, and agentic AI workflows. - Tooling and Transparency
The compiler itself is small, transparent, and hackable — deliberately so. It encourages learning, experimentation, and extension: new backends, richer standard libraries, and even formal verification of financial flows. - CSV and Reporting Integration
The accompanying reporting engine showcases practical interoperability: Cobol-Next computations can be embedded in C pipelines for CSV/JSON analytics, effectively turning domain logic into executable financial reports.
Cobol-Next points toward a renaissance of domain-specific, semantically rich programming.
It suggests that future enterprise systems need not be monolithic COBOL rewrites nor ad-hoc script collections — but can be expressed in compact, type-safe, readable DSLs that compile down to efficient native code or LLVM IR.
In short, Cobol-Next re-awakens the spirit of COBOL for a new generation: human-centric in syntax, mathematically rigorous in semantics, and deeply compatible with today’s software architecture landscape.