Discounted cash flow: present value of a single flow, net present value of a cash-flow stream, and a simple payback-period count
Discounting over an index range, sum() of derived discounted flows.
A Soma program from the finance part of the verified corpus: 57 lines, cells DCF, DCFTests. It passes soma check with soma 3.2.4.
How it is built
The cell DCF declares 4 signals in its face: present_value(cash: Float, rate: Float, period: Int) -> Float, npv(rate: Float, flows: List) -> Float, payback_period(flows: List) -> Int and profitable(rate: Float, flows: List) -> Bool. Handlers: present_value (line 14), npv (line 19), payback_period (line 28) and profitable (line 40).
The file also holds 1 test cell (DCFTests) with 4 assertions, run by soma test.
The source
npv.cell
// Discounted cash flow: present value of a single flow, net present value
// of a cash-flow stream, and a simple payback-period count. Discounting
// over an index range, sum() of derived discounted flows.
cell DCF {
face {
signal present_value(cash: Float, rate: Float, period: Int) -> Float
signal npv(rate: Float, flows: List) -> Float
signal payback_period(flows: List) -> Int
signal profitable(rate: Float, flows: List) -> Bool
}
// PV = CF / (1+r)^t
on present_value(cash: Float, rate: Float, period: Int) {
return cash / pow(1.0 + rate, period)
}
// NPV = sum_t CF_t / (1+r)^t, flows[0] at t=0 (usually the outlay)
on npv(rate: Float, flows: List) {
let total = 0.0
for t in range(0, len(flows)) {
total = total + present_value(flows[t], rate, t)
}
return total
}
// first period index where cumulative (undiscounted) flow turns non-negative
on payback_period(flows: List) {
let cum = 0.0
let result = -1
for t in range(0, len(flows)) {
cum = cum + flows[t]
if cum >= 0.0 && result == -1 {
result = t
}
}
return result
}
on profitable(rate: Float, flows: List) {
return npv(rate, flows) > 0.0
}
}
cell test DCFTests {
rules {
// PV of 110 one year out at 10% = 100
assert abs(present_value(110.0, 0.10, 1) - 100.0) < 0.0001
// invest 100 now, get 60 + 60 → positive NPV at 10%
assert npv(0.10, list(-100.0, 60.0, 60.0)) > 0.0
// a money-losing project
assert profitable(0.10, list(-100.0, 30.0, 30.0)) == false
// payback: -100, 60, 60 → cumulative crosses zero at period 2
assert payback_period(list(-100.0, 60.0, 60.0)) == 2
}
}What the toolchain says
$ soma check npv.cell
✓ All checks passed.$ soma test npv.cell
test DCFTests ...
✓ assert abs(present_value(110.0, 0.10, 1) - 100.0) < 0.0001
✓ assert npv(0.10, list(-100.0, 60.0, 60.0)) > 0.0
✓ assert profitable(0.10, list(-100.0, 30.0, 30.0)) == false
✓ assert payback_period(list(-100.0, 60.0, 60.0)) == 2
4 tests: 4 passed, 0 failed$ soma verify npv.cell
Verifying npv.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)