aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--LICENSE21
-rw-r--r--README.md17
-rw-r--r--lpd.zig87
4 files changed, 127 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1be49ec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+lpd
+lpd.o
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ec89cc6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Adam House
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..034231f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# lpd
+
+simple **l**ump-sum **p**ay**d**own calculator
+
+`lpd` reports how many months remain on a given loan, based on the provided
+principal balance, interest rate and monthly payment, then advises the payment
+amounts that, if made now in addition to the ordinary monthly payments, would
+reduce the loan term to the specified number of months. It will report up to
+twelve of these.
+
+Because math is cool, we avoid recursion.
+
+## Installation & Usage
+
+Compile with [Zig](https://ziglang.org/), i.e. `zig build-exe lpd.zig`.
+
+Learn to use with `lpd -h`.
diff --git a/lpd.zig b/lpd.zig
new file mode 100644
index 0000000..943b0d9
--- /dev/null
+++ b/lpd.zig
@@ -0,0 +1,87 @@
+const std = @import("std");
+const math = std.math;
+
+// Calculates number of months to pay off a balance
+fn n(b: f64, r: f64, p: f64) f64 {
+ if (r == 0) {
+ return @ceil(b / p);
+ }
+ const i = r / 12;
+ return @ceil(-(math.log(f64, math.e, 1 - (i * b / p))) /
+ math.log(f64, math.e, 1 + i));
+}
+
+// Calculates lump sum required to pay off a balance in specified time `t`
+fn l(b: f64, r: f64, p: f64, t: f64) f64 {
+ if (r == 0) {
+ return @max(b - p * t, 0);
+ }
+ const i = r / 12;
+ return b - (p * (1 - math.pow(f64, 1 + i, -t)) / i);
+}
+
+fn u() void {
+ std.debug.print("usage: lpd -b <balance> -p <payment> -r <rate>\n" ++
+ " -b Principal balance\n" ++
+ " -p Monthly payment\n" ++
+ " -r Annual interest rate\n" ++
+ " -h Show this help message\n", .{});
+}
+
+pub fn main() !void {
+ const a = try std.process.argsAlloc(std.heap.page_allocator);
+ defer std.process.argsFree(std.heap.page_allocator, a);
+
+ var b: f64 = 0;
+ var p: f64 = 0;
+ var r: f64 = 0;
+
+ var i: usize = 1;
+ while (i < a.len) : (i += 1) {
+ const f = a[i];
+ if (std.mem.eql(u8, f, "-h")) {
+ u();
+ return;
+ } else if (i + 1 < a.len) {
+ const v = std.fmt.parseFloat(f64, a[i + 1]) catch {
+ u();
+ return;
+ };
+ if (std.mem.eql(u8, f, "-b")) {
+ b = v;
+ } else if (std.mem.eql(u8, f, "-p")) {
+ p = v;
+ } else if (std.mem.eql(u8, f, "-r")) {
+ r = v / 100;
+ } else {
+ u();
+ return;
+ }
+ i += 1;
+ } else {
+ u();
+ return;
+ }
+ }
+
+ if (b == 0 or p == 0) {
+ u();
+ return;
+ }
+
+ if (p <= r / 12 * b) {
+ std.debug.print("payment must cover interest\n", .{});
+ return;
+ }
+
+ const m = n(b, r, p);
+ try std.io.getStdOut().writer().print("{d} remain\n", .{m});
+
+ const min = @max(m - 12, 0);
+ var j = m - 1;
+ while (j >= min) : (j -= 1) {
+ const s = l(b, r, p, j);
+ try std.io.getStdOut().writer()
+ .print("pay ${d:.2} for {d}\n", .{ s, j });
+ }
+}