C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
iloan.c
Go to the documentation of this file.
1/** @file iloan.c
2 @brief Installment Loan Calculator
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09
8 */
9
10#include <math.h>
11#include <signal.h>
12#include <stdbool.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <unistd.h>
17
18#ifdef __USE_GNU_GETTEXT
19#include <libintl.h>
20#define _(String) gettext(String)
21#define gettext_noop(String) String
22#define N_(String) gettext_noop(String)
23#else
24#define _(String) (String)
25#define N_(String) String
26#define textdomain(Domain)
27#define bindtextdomain(Package, Directory)
28#endif
29
30#define FALSE 0
31#define TRUE 1
32char in_str[BUFSIZ + 1];
33
34void numbers(char *d, char *s);
35double calculate_i(double, double, double);
36double calculate_n(double, double, double);
37double calculate_pmt(double, double, double);
38double calculate_pv(double, double, double);
39int is_numeric(char *);
40void accept_str(char *s);
41char *format_currency(float);
42char *format_interest(float);
43double accept_pv();
44double accept_n();
45double accept_i();
46double accept_pmt();
47void error_press_any_key(char *);
48void ABEND(int);
49int f_pv = 0;
50int f_n = 0;
51int f_i = 0;
52int f_pmt = 0;
53bool f_quiet = false;
54
55/** @brief iloan is a trivial application to demonstrate how a command-line
56 program can be integrated into C-Menu Form with simple file, argument, or pipe
57 i-o.
58 @details Positional arguments:
59 present_value The present value of the loan (the amount borrowed).
60 number_of_payments The total number of payments to be made.
61 interest_rate The annual interest rate (as a percentage).
62 payment_amount The amount of each payment.
63 The program calculates the missing value based on the three provided
64 values. For example, if the present value, number of payments, and interest
65 rate are provided, it will calculate the payment amount. If the present value,
66 interest rate, and payment amount are provided, it will calculate the number
67 of payments, and so on.
68 The program can be used in a non-interactive way by passing the four
69 values as arguments, with the value to be calculated set to 0. For example, to
70 calculate the payment amount for a $10,000 loan with a 5% annual interest rate
71 and 60 monthly payments, you could run:
72 @code
73 iloan 10000 60 5 0
74 @endcode
75 This is proof-of-concept code, and is not intended for production use.
76 It is not designed to be robust, and does not handle all edge cases or input
77 errors. It is intended solely to demonstrate how a simple command-line program
78 can be integrated into C-Menu Form.
79 In the future, more sophisticated abstractions, such as async event
80 handlers, serialization, rpc, database, and a standardized plugin interface
81 will be integrated into C-Menu.
82 Feel free to modify and enhance this code as needed, but please do not
83 use it in production without proper testing and validation. It is provided
84 as-is without any warranty or support. Use at your own risk.
85*/
86int main(int argc, char **argv) {
87 double pv = 0, pmt = 0, i = 0, n = 0;
88 char tmp_str[BUFSIZ];
89
90 signal(SIGINT, ABEND);
91 signal(SIGQUIT, ABEND);
92 signal(SIGHUP, ABEND);
93
94 if (argc > 1 &&
95 ((strcmp(argv[1], _("--help")) == 0 || strcmp(argv[1], "-h") == 0) ||
96 argc < 4)) {
97 printf(_("Usage: iloan [present_value number_of_payments interest_rate payment_amount]\n\n"));
98 exit(EXIT_SUCCESS);
99 }
100 if (argc > 4) {
101 numbers(tmp_str, argv[1]);
102 sscanf(tmp_str, "%lf", &pv);
103 sscanf(argv[2], "%lf", &n);
104 sscanf(argv[3], "%lf", &i);
105 numbers(tmp_str, argv[4]);
106 sscanf(tmp_str, "%lf", &pmt);
107 if (pv != 0)
108 f_pv = 1;
109 if (n != 0)
110 f_n = 1;
111 if (i != 0)
112 f_i = 1;
113 if (pmt != 0)
114 f_pmt = 1;
115 if (f_pv + f_n + f_i + f_pmt < 3) {
117 _("Error: At least three values must be greater than zero."));
118 exit(EXIT_FAILURE);
119 }
120 f_quiet = true;
121 } else {
122 if (argc != 1) {
123 printf(_("Usage: iloan [present_value number_of_payments interest_rate payment_amount]\n\n"));
124 exit(EXIT_FAILURE);
125 } else {
126 if (argc == 1) {
127 printf(_("\nInstallment Loan Calculator\n\n"));
128 printf(_("Three of these values must be greater than 0. The "
129 "field\n"));
130 printf(_("with a value of 0 will be calculated.\n\n"));
131 while (f_pv + f_n + f_i + f_pmt < 3) {
132 if (pv == 0) {
133 pv = accept_pv();
134 if (pv != 0)
135 f_pv = 1;
136 }
137 if (n == 0) {
138 n = accept_n();
139 if (n != 0)
140 f_n = 1;
141 }
142 if (i == 0) {
143 i = accept_i();
144 if (i != 0)
145 f_i = 1;
146 }
147 if (pmt == 0) {
148 pmt = accept_pmt();
149 if (pmt != 0)
150 f_pmt = 1;
151 }
152 if (f_pv + f_n + f_i + f_pmt < 3) {
153 error_press_any_key(_("Error: At least three values must "
154 "be greater than zero."));
155 }
156 }
157 printf("\nYou entered:\n\n");
158 if (pv != 0)
159 printf(_("Present Value - - - - - -> %s\n"),
161 if (n != 0)
162 printf(_("Number of Payments - - -> %s\n"),
164 if (i != 0)
165 printf(_("Interest Rate - - - - - -> %s\n"),
167 if (pmt != 0)
168 printf(_("Payment Amount - - - - -> %s\n"),
170 printf(_("\n\nCalculation result:\n\n"));
171 }
172 }
173 }
174 if (pv == 0)
175 pv = calculate_pv(n, i, pmt);
176 else if (n == 0)
177 n = calculate_n(pv, i, pmt);
178 else if (i == 0)
179 i = calculate_i(pv, n, pmt);
180 else if (pmt == 0)
181 pmt = calculate_pmt(pv, n, i);
182
183 if (f_quiet) {
184 printf("%s\n", format_currency(pv));
185 printf("%s\n", format_currency(n));
186 printf("%s\n", format_interest(i));
187 printf("%s\n", format_currency(pmt));
188 }
189 signal(SIGINT, SIG_DFL);
190 signal(SIGQUIT, SIG_DFL);
191 signal(SIGHUP, SIG_DFL);
192}
193/** @brief The following functions are used to accept user input for the present
194 value, number of payments, interest rate, and payment amount. They validate
195 the input to ensure it is numeric and meets the required conditions (e.g., non-negative).
196 If the input is invalid, an error message is displayed, and the user is prompted to enter the value again.
197*/
198double accept_pv() {
199 double pv;
200 while (1) {
201 accept_str(_("Present Value - - -> "));
202 if (in_str[0] == '\0') {
203 pv = 0;
204 break;
205 } else {
206 if (is_numeric(in_str)) {
207 pv = atof(in_str);
208 if (pv < 0)
209 error_press_any_key(_("Present Value can't be less than 0"));
210 else
211 break;
212 } else
213 error_press_any_key(_("Present Value must be numeric"));
214 }
215 }
216 return (pv);
217}
218/** @brief The accept_n function prompts the user to enter the number of payments for the loan. It validates the input to ensure it is numeric and non-negative. If the input is invalid, an error message is displayed, and the user is prompted to enter the value again until a valid input is provided.
219 */
220double accept_n() {
221 double n;
222 while (1) {
223 accept_str(_("Number of Payments > "));
224 if (is_numeric(in_str)) {
225 n = atof(in_str);
226 if (n < 0)
227 error_press_any_key(_("Number of Payments can't be less than 0"));
228 else
229 break;
230 } else
231 error_press_any_key(_("Number of Payments must be numeric"));
232 }
233 return (n);
234}
235/** @brief The accept_i function prompts the user to enter the annual interest rate for the loan. It validates the input to ensure it is numeric and non-negative. If the input is invalid, an error message is displayed, and the user is prompted to enter the value again until a valid input is provided.
236 */
237double accept_i() {
238 double i;
239 while (1) {
240 accept_str(_("Rate (annual) - - -> "));
241 if (in_str[0] == '\0') {
242 i = 0;
243 break;
244 } else {
245 if (is_numeric(in_str)) {
246 i = atof(in_str);
247 if (i < 0)
248 error_press_any_key(_("interest Rate can't be less than 0"));
249 else
250 break;
251 } else
252 error_press_any_key(_("Interest Rate must be numeric"));
253 }
254 }
255 return (i);
256}
257/** brief The accept_pmt function prompts the user to enter the payment amount for the loan. It validates the input to ensure it is numeric and non-negative. If the input is invalid, an error message is displayed, and the user is prompted to enter the value again until a valid input is provided.
258 */
259double accept_pmt() {
260 double pmt;
261 while (1) {
262 accept_str(_("Payment Amount - -> "));
263 if (in_str[0] == '\0') {
264 pmt = 0;
265 break;
266 } else {
267 if (is_numeric(in_str)) {
268 pmt = atof(in_str);
269 break;
270 } else
271 error_press_any_key(_("Payment Amount must be numeric"));
272 }
273 }
274 return (pmt);
275}
276/** @brief The error_press_any_key function is a utility function that displays an error message to the user and waits for them to press any key before continuing. It takes a string argument that contains the error message to be displayed. After the user presses a key, it prints two newlines for formatting.
277 */
278void error_press_any_key(char *s) {
279 printf("%s", s);
280 getc(stdin);
281 printf("\n\n");
282}
283/** @brief The calculate_pv function calculates the present value of a loan based on the number of payments, interest rate, and payment amount. It uses the formula for the present value of an annuity to compute the result. If any of the input values are zero, it displays an error message and prompts the user to provide valid inputs.
284 */
285double calculate_pv(double n, double i, double pmt) {
286 double i1, pv;
287
288 if (n == 0 || i == 0 || pmt == 0)
290 _("3 non-zero values required to calculate Present Value"));
291 i1 = i / 1200;
292 pv = pmt * (1 - pow(1 + i1, -n)) / i1;
293 if (!f_quiet)
294 printf(_("Present Value - - - - - -> %s\n"), format_currency(pv));
295 return (pv);
296}
297/** @brief The calculate_n function calculates the number of payments required to pay off a loan based on the present value, interest rate, and payment amount. It uses logarithmic functions to compute the result. If any of the input values are zero, it displays an error message and prompts the user to provide valid inputs.
298 */
299double calculate_n(double pv, double i, double pmt) {
300 double i1, n;
301 if (pv == 0 || i == 0 || pmt == 0)
302 error_press_any_key(_("3 non-zero values required to calculate "
303 "Number of Payments"));
304 i1 = i / 1200;
305 n = -log(1 - pv * i1 / pmt) / log(1 + i1);
306 if (!f_quiet)
307 printf(_("Number of Payments - - -> %s\n"), format_currency(n));
308 return (n);
309}
310/** @brief The calculate_i function calculates the annual interest rate for a loan based on the present value, number of payments, and payment amount. It uses an iterative method to find the interest rate that satisfies the loan equation. If any of the input values are zero, it displays an error message and prompts the user to provide valid inputs.
311 */
312double calculate_i(double pv, double n, double pmt) {
313 double i1 = 0, i;
314 double delta = 0.0000001;
315 double xdelta;
316 double fdelta;
317 double fprimi;
318 double ffact;
319 double fi;
320 double fman;
321 double fmann;
322 if (pv == 0 || n == 0 || pmt == 0)
324 _("3 non-zero values required to calculate Interest Rate"));
325 ffact = pv / pmt;
326 xdelta = 0;
327 if (ffact < n) {
328 i1 = 0.0125;
329 xdelta = 0.9;
330 }
331 while (xdelta > delta) {
332 fman = 1 / (1 + i1);
333 fmann = pow(fman, n);
334 fi = ffact * i1 + fmann - 1;
335 fprimi = ffact - n * fmann * fman;
336 fdelta = fi / fprimi;
337 i1 = i1 - fdelta;
338 if (fdelta < 0)
339 xdelta = -fdelta;
340 else
341 xdelta = fdelta;
342 }
343 i = i1 * 1200;
344 if (!f_quiet)
345 printf(_("interest Rate - - - - - -> %s\n"), format_interest(i));
346 return (i);
347}
348/** @brief The calculate_pmt function calculates the payment amount for a loan based on the present value, number of payments, and interest rate. It uses the formula for the payment amount of an annuity to compute the result. If any of the input values are zero, it displays an error message and prompts the user to provide valid inputs.
349 */
350double calculate_pmt(double pv, double n, double i) {
351 double i1, pmt;
352 if (pv == 0 || n == 0 || i == 0)
354 _("3 non-zero values required to calculate Payment Amount"));
355 i1 = i / 1200;
356 pmt = pv * i1 / (1 - pow(1 + i1, -n));
357 if (!f_quiet)
358 printf(_("Payment Amount - - - - -> %s\n"), format_currency(pmt));
359 return (pmt);
360}
361/** @brief The is_numeric function checks if a given string consists of numeric characters, including digits, decimal points, and commas. It iterates through each character in the string and returns FALSE if it encounters any character that is not a digit, a decimal point, or a comma. If all characters are valid, it returns TRUE.
362 */
363int is_numeric(char *s) {
364 char c;
365
366 while ((c = *s++) != '\0' && c != '\n')
367 if ((c < '0' || c > '9') && c != '.' && c != ',')
368 return (FALSE);
369 return (TRUE);
370}
371/** @brief The accept_str function prompts the user with a given string and reads input from the standard input into a buffer. It uses the fprintf function to display the prompt and the read function to capture the user's input. The input is stored in the global buffer in_str, which can be used by other functions for further processing.
372 */
373void accept_str(char *s) {
374 fprintf(stderr, "%s", s);
375 read(0, in_str, BUFSIZ);
376}
377/** @brief The format_currency function takes a floating-point number as input and formats it as a currency string. It adds commas as thousand separators and ensures that the number is displayed with two decimal places. The function uses a static buffer to store the formatted string and returns a pointer to that buffer. The formatted string is right-aligned and padded with spaces if necessary.
378 */
379char *format_currency(float a) {
380 int digit_count, left_of_point;
381 static char sstr[80];
382 static char fstr[80];
383 char *src_ptr, *dst_ptr, *sptr, *FPtr;
384
385 sprintf(sstr, "%-18.2f", a);
386 sptr = sstr;
387 src_ptr = sstr;
388 FPtr = fstr;
389 dst_ptr = fstr;
390 while (*src_ptr++ != '\0')
391 ;
392 src_ptr -= 2;
393 left_of_point = FALSE;
394 digit_count = 0;
395 while (src_ptr >= sptr) {
396 if (left_of_point) {
397 if (*src_ptr >= '0' && *src_ptr <= '9') {
398 if (digit_count == 3) {
399 *dst_ptr++ = ',';
400 digit_count = 1;
401 } else
402 digit_count++;
403 }
404 } else if (*src_ptr == '.')
405 left_of_point = TRUE;
406 *dst_ptr++ = *src_ptr--;
407 }
408 src_ptr = dst_ptr - 1;
409 dst_ptr = sptr;
410 while (src_ptr >= FPtr)
411 *dst_ptr++ = *src_ptr--;
412 while (*--dst_ptr == ' ')
413 ;
414 *++dst_ptr = '\0';
415 return (sptr);
416}
417/** @brief The format_interest function takes a floating-point number representing an interest rate and formats it as a string with five decimal places. It uses a static buffer to store the formatted string and returns a pointer to that buffer. The formatted string is left-aligned and padded with spaces if necessary.
418 */
419char *format_interest(float a) {
420 static char sstr[80];
421 char *s;
422
423 sprintf(sstr, "%-3.5f", a);
424 s = sstr;
425 return (s);
426}
427/** @brief The ABEND function is a signal handler that is called when the program receives certain signals (e.g., SIGINT, SIGQUIT, SIGHUP). It takes an integer argument representing the signal number and prints an error message indicating that an abnormal end (ABEND) has occurred, along with the signal number. After displaying the message, it exits the program with a failure status.
428 */
429void ABEND(int e) {
430 printf(_("ABEND: Error %d:\n"), e);
431 exit(EXIT_FAILURE);
432}
433/** @brief The numbers function takes two character pointers as arguments: a destination pointer (d) and a source pointer (s). It iterates through the characters in the source string (s) and copies only the numeric characters (digits, decimal points, and minus signs) to the destination string (d). The function effectively filters out any non-numeric characters from the source string and constructs a new string containing only the valid numeric characters. Finally, it null-terminates the destination string.
434 */
435void numbers(char *d, char *s) {
436 while (*s != '\0') {
437 if (*s == '-' || *s == '.' || (*s >= '0' && *s <= '9'))
438 *d++ = *s++;
439 else
440 s++;
441 }
442 *d = '\0';
443}
#define BUFSIZ
Definition view.h:38
int f_n
Definition iloan.c:50
double accept_n()
The accept_n function prompts the user to enter the number of payments for the loan....
Definition iloan.c:220
double calculate_i(double, double, double)
The calculate_i function calculates the annual interest rate for a loan based on the present value,...
Definition iloan.c:312
void numbers(char *d, char *s)
The numbers function takes two character pointers as arguments: a destination pointer (d) and a sourc...
Definition iloan.c:435
double accept_pmt()
Definition iloan.c:259
bool f_quiet
Definition iloan.c:53
double accept_pv()
The following functions are used to accept user input for the present value, number of payments,...
Definition iloan.c:198
void ABEND(int)
The ABEND function is a signal handler that is called when the program receives certain signals (e....
Definition iloan.c:429
double calculate_pmt(double, double, double)
The calculate_pmt function calculates the payment amount for a loan based on the present value,...
Definition iloan.c:350
void error_press_any_key(char *)
The error_press_any_key function is a utility function that displays an error message to the user and...
Definition iloan.c:278
int f_i
Definition iloan.c:51
void accept_str(char *s)
The accept_str function prompts the user with a given string and reads input from the standard input ...
Definition iloan.c:373
double calculate_pv(double, double, double)
The calculate_pv function calculates the present value of a loan based on the number of payments,...
Definition iloan.c:285
char * format_interest(float)
The format_interest function takes a floating-point number representing an interest rate and formats ...
Definition iloan.c:419
double accept_i()
The accept_i function prompts the user to enter the annual interest rate for the loan....
Definition iloan.c:237
int f_pv
Definition iloan.c:49
int is_numeric(char *)
The is_numeric function checks if a given string consists of numeric characters, including digits,...
Definition iloan.c:363
int f_pmt
Definition iloan.c:52
#define TRUE
Definition iloan.c:31
#define FALSE
Definition iloan.c:30
char * format_currency(float)
The format_currency function takes a floating-point number as input and formats it as a currency stri...
Definition iloan.c:379
char in_str[BUFSIZ+1]
Definition iloan.c:32
double calculate_n(double, double, double)
The calculate_n function calculates the number of payments required to pay off a loan based on the pr...
Definition iloan.c:299
#define _(String)
Definition amort.c:23
int main(int argc, char **argv)
Capture the current terminal settings for later restoration.
Definition amort.c:41