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#define FALSE 0
19#define TRUE 1
20char in_str[BUFSIZ + 1];
21
22void numbers(char *d, char *s);
23double calculate_i(double, double, double);
24double calculate_n(double, double, double);
25double calculate_pmt(double, double, double);
26double calculate_pv(double, double, double);
27int is_numeric(char *);
28void accept_str(char *s);
29char *format_currency(float);
30char *format_interest(float);
31double accept_pv();
32double accept_n();
33double accept_i();
34double accept_pmt();
35void error_press_any_key(char *);
36void ABEND(int);
37int f_pv = 0;
38int f_n = 0;
39int f_i = 0;
40int f_pmt = 0;
41bool f_quiet = false;
42
43/** @brief iloan is a trivial application to demonstrate how a command-line
44 program can be integrated into C-Menu Form with simple file, argument, or pipe
45 i-o.
46 @details Positional arguments:
47 present_value The present value of the loan (the amount borrowed).
48 number_of_payments The total number of payments to be made.
49 interest_rate The annual interest rate (as a percentage).
50 payment_amount The amount of each payment.
51 The program calculates the missing value based on the three provided
52 values. For example, if the present value, number of payments, and interest
53 rate are provided, it will calculate the payment amount. If the present value,
54 interest rate, and payment amount are provided, it will calculate the number
55 of payments, and so on.
56 The program can be used in a non-interactive way by passing the four
57 values as arguments, with the value to be calculated set to 0. For example, to
58 calculate the payment amount for a $10,000 loan with a 5% annual interest rate
59 and 60 monthly payments, you could run:
60 @code
61 iloan 10000 60 5 0
62 @endcode
63 This is proof-of-concept code, and is not intended for production use.
64 It is not designed to be robust, and does not handle all edge cases or input
65 errors. It is intended solely to demonstrate how a simple command-line program
66 can be integrated into C-Menu Form.
67 In the future, more sophisticated abstractions, such as async event
68 handlers, serialization, rpc, database, and a standardized plugin interface
69 will be integrated into C-Menu.
70 Feel free to modify and enhance this code as needed, but please do not
71 use it in production without proper testing and validation. It is provided
72 as-is without any warranty or support. Use at your own risk.
73*/
74int main(int argc, char **argv) {
75 double pv = 0, pmt = 0, i = 0, n = 0;
76 char tmp_str[BUFSIZ];
77
78 signal(SIGINT, ABEND);
79 signal(SIGQUIT, ABEND);
80 signal(SIGHUP, ABEND);
81
82 if (argc > 1 &&
83 ((strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0) ||
84 argc < 4)) {
85 printf("Usage: iloan [present_value number_of_payments "
86 "interest_rate payment_amount]\n\n");
87 exit(EXIT_SUCCESS);
88 }
89 if (argc > 4) {
90 numbers(tmp_str, argv[1]);
91 sscanf(tmp_str, "%lf", &pv);
92 sscanf(argv[2], "%lf", &n);
93 sscanf(argv[3], "%lf", &i);
94 numbers(tmp_str, argv[4]);
95 sscanf(tmp_str, "%lf", &pmt);
96 if (pv != 0)
97 f_pv = 1;
98 if (n != 0)
99 f_n = 1;
100 if (i != 0)
101 f_i = 1;
102 if (pmt != 0)
103 f_pmt = 1;
104 if (f_pv + f_n + f_i + f_pmt < 3) {
106 "Error: At least three values must be greater than zero.");
107 exit(EXIT_FAILURE);
108 }
109 f_quiet = true;
110 } else {
111 if (argc != 1) {
112 printf("Usage: iloan [present_value number_of_payments "
113 "interest_rate payment_amount]\n\n");
114 exit(EXIT_FAILURE);
115 } else {
116 if (argc == 1) {
117 printf("\nInstallment Loan Calculator\n\n");
118 printf("Three of these values must be greater than 0. The "
119 "field\n");
120 printf("with a value of 0 will be calculated.\n\n");
121 while (f_pv + f_n + f_i + f_pmt < 3) {
122 if (pv == 0) {
123 pv = accept_pv();
124 if (pv != 0)
125 f_pv = 1;
126 }
127 if (n == 0) {
128 n = accept_n();
129 if (n != 0)
130 f_n = 1;
131 }
132 if (i == 0) {
133 i = accept_i();
134 if (i != 0)
135 f_i = 1;
136 }
137 if (pmt == 0) {
138 pmt = accept_pmt();
139 if (pmt != 0)
140 f_pmt = 1;
141 }
142 if (f_pv + f_n + f_i + f_pmt < 3) {
143 error_press_any_key("Error: At least three values must "
144 "be greater than zero.");
145 }
146 }
147 printf("\nYou entered:\n\n");
148 if (pv != 0)
149 printf("Present Value - - - - - -> %s\n",
151 if (n != 0)
152 printf("Number of Payments - - -> %s\n",
154 if (i != 0)
155 printf("Interest Rate - - - - - -> %s\n",
157 if (pmt != 0)
158 printf("Payment Amount - - - - -> %s\n",
160 printf("\n\nCalculation result:\n\n");
161 }
162 }
163 }
164 if (pv == 0)
165 pv = calculate_pv(n, i, pmt);
166 else if (n == 0)
167 n = calculate_n(pv, i, pmt);
168 else if (i == 0)
169 i = calculate_i(pv, n, pmt);
170 else if (pmt == 0)
171 pmt = calculate_pmt(pv, n, i);
172
173 if (f_quiet) {
174 printf("%s\n", format_currency(pv));
175 printf("%s\n", format_currency(n));
176 printf("%s\n", format_interest(i));
177 printf("%s\n", format_currency(pmt));
178 }
179 signal(SIGINT, SIG_DFL);
180 signal(SIGQUIT, SIG_DFL);
181 signal(SIGHUP, SIG_DFL);
182}
183/** @brief The following functions are used to accept user input for the present
184 value, number of payments, interest rate, and payment amount. They validate
185 the input to ensure it is numeric and meets the required conditions (e.g., non-negative).
186 If the input is invalid, an error message is displayed, and the user is prompted to enter the value again.
187*/
188double accept_pv() {
189 double pv;
190 while (1) {
191 accept_str("Present Value - - -> ");
192 if (in_str[0] == '\0') {
193 pv = 0;
194 break;
195 } else {
196 if (is_numeric(in_str)) {
197 pv = atof(in_str);
198 if (pv < 0)
199 error_press_any_key("Present Value can't be less than 0");
200 else
201 break;
202 } else
203 error_press_any_key("Present Value must be numeric");
204 }
205 }
206 return (pv);
207}
208/** @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.
209 */
210double accept_n() {
211 double n;
212 while (1) {
213 accept_str("Number of Payments > ");
214 if (is_numeric(in_str)) {
215 n = atof(in_str);
216 if (n < 0)
217 error_press_any_key("Number of Payments can't be less than 0");
218 else
219 break;
220 } else
221 error_press_any_key("Number of Payments must be numeric");
222 }
223 return (n);
224}
225/** @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.
226 */
227double accept_i() {
228 double i;
229 while (1) {
230 accept_str("Rate (annual) - - -> ");
231 if (in_str[0] == '\0') {
232 i = 0;
233 break;
234 } else {
235 if (is_numeric(in_str)) {
236 i = atof(in_str);
237 if (i < 0)
238 error_press_any_key("interest Rate can't be less than 0");
239 else
240 break;
241 } else
242 error_press_any_key("Interest Rate must be numeric");
243 }
244 }
245 return (i);
246}
247/** 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.
248 */
249double accept_pmt() {
250 double pmt;
251 while (1) {
252 accept_str("Payment Amount - -> ");
253 if (in_str[0] == '\0') {
254 pmt = 0;
255 break;
256 } else {
257 if (is_numeric(in_str)) {
258 pmt = atof(in_str);
259 break;
260 } else
261 error_press_any_key("Payment Amount must be numeric");
262 }
263 }
264 return (pmt);
265}
266/** @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.
267 */
268void error_press_any_key(char *s) {
269 printf("%s", s);
270 getc(stdin);
271 printf("\n\n");
272}
273/** @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.
274 */
275double calculate_pv(double n, double i, double pmt) {
276 double i1, pv;
277
278 if (n == 0 || i == 0 || pmt == 0)
280 "3 non-zero values required to calculate Present Value");
281 i1 = i / 1200;
282 pv = pmt * (1 - pow(1 + i1, -n)) / i1;
283 if (!f_quiet)
284 printf("Present Value - - - - - -> %s\n", format_currency(pv));
285 return (pv);
286}
287/** @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.
288 */
289double calculate_n(double pv, double i, double pmt) {
290 double i1, n;
291 if (pv == 0 || i == 0 || pmt == 0)
292 error_press_any_key("3 non-zero values required to calculate "
293 "Number of Payments");
294 i1 = i / 1200;
295 n = -log(1 - pv * i1 / pmt) / log(1 + i1);
296 if (!f_quiet)
297 printf("Number of Payments - - -> %s\n", format_currency(n));
298 return (n);
299}
300/** @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.
301 */
302double calculate_i(double pv, double n, double pmt) {
303 double i1 = 0, i;
304 double delta = 0.0000001;
305 double xdelta;
306 double fdelta;
307 double fprimi;
308 double ffact;
309 double fi;
310 double fman;
311 double fmann;
312 if (pv == 0 || n == 0 || pmt == 0)
314 "3 non-zero values required to calculate Interest Rate");
315 ffact = pv / pmt;
316 xdelta = 0;
317 if (ffact < n) {
318 i1 = 0.0125;
319 xdelta = 0.9;
320 }
321 while (xdelta > delta) {
322 fman = 1 / (1 + i1);
323 fmann = pow(fman, n);
324 fi = ffact * i1 + fmann - 1;
325 fprimi = ffact - n * fmann * fman;
326 fdelta = fi / fprimi;
327 i1 = i1 - fdelta;
328 if (fdelta < 0)
329 xdelta = -fdelta;
330 else
331 xdelta = fdelta;
332 }
333 i = i1 * 1200;
334 if (!f_quiet)
335 printf("interest Rate - - - - - -> %s\n", format_interest(i));
336 return (i);
337}
338/** @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.
339 */
340double calculate_pmt(double pv, double n, double i) {
341 double i1, pmt;
342 if (pv == 0 || n == 0 || i == 0)
344 "3 non-zero values required to calculate Payment Amount");
345 i1 = i / 1200;
346 pmt = pv * i1 / (1 - pow(1 + i1, -n));
347 if (!f_quiet)
348 printf("Payment Amount - - - - -> %s\n", format_currency(pmt));
349 return (pmt);
350}
351/** @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.
352 */
353int is_numeric(char *s) {
354 char c;
355
356 while ((c = *s++) != '\0' && c != '\n')
357 if ((c < '0' || c > '9') && c != '.' && c != ',')
358 return (FALSE);
359 return (TRUE);
360}
361/** @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.
362 */
363void accept_str(char *s) {
364 fprintf(stderr, "%s", s);
365 read(0, in_str, BUFSIZ);
366}
367/** @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.
368 */
369char *format_currency(float a) {
370 int digit_count, left_of_point;
371 static char sstr[80];
372 static char fstr[80];
373 char *src_ptr, *dst_ptr, *sptr, *FPtr;
374
375 sprintf(sstr, "%-18.2f", a);
376 sptr = sstr;
377 src_ptr = sstr;
378 FPtr = fstr;
379 dst_ptr = fstr;
380 while (*src_ptr++ != '\0')
381 ;
382 src_ptr -= 2;
383 left_of_point = FALSE;
384 digit_count = 0;
385 while (src_ptr >= sptr) {
386 if (left_of_point) {
387 if (*src_ptr >= '0' && *src_ptr <= '9') {
388 if (digit_count == 3) {
389 *dst_ptr++ = ',';
390 digit_count = 1;
391 } else
392 digit_count++;
393 }
394 } else if (*src_ptr == '.')
395 left_of_point = TRUE;
396 *dst_ptr++ = *src_ptr--;
397 }
398 src_ptr = dst_ptr - 1;
399 dst_ptr = sptr;
400 while (src_ptr >= FPtr)
401 *dst_ptr++ = *src_ptr--;
402 while (*--dst_ptr == ' ')
403 ;
404 *++dst_ptr = '\0';
405 return (sptr);
406}
407/** @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.
408 */
409char *format_interest(float a) {
410 static char sstr[80];
411 char *s;
412
413 sprintf(sstr, "%-3.5f", a);
414 s = sstr;
415 return (s);
416}
417/** @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.
418 */
419void ABEND(int e) {
420 printf("ABEND: Error %d:\n", e);
421 exit(EXIT_FAILURE);
422}
423/** @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.
424 */
425void numbers(char *d, char *s) {
426 while (*s != '\0') {
427 if (*s == '-' || *s == '.' || (*s >= '0' && *s <= '9'))
428 *d++ = *s++;
429 else
430 s++;
431 }
432 *d = '\0';
433}
#define BUFSIZ
Definition view.h:31
char in_str[BUFSIZ+1]
Definition iloan.c:20
int f_n
Definition iloan.c:38
double accept_n()
The accept_n function prompts the user to enter the number of payments for the loan....
Definition iloan.c:210
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:302
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:425
double accept_pmt()
Definition iloan.c:249
bool f_quiet
Definition iloan.c:41
double accept_pv()
The following functions are used to accept user input for the present value, number of payments,...
Definition iloan.c:188
void ABEND(int)
The ABEND function is a signal handler that is called when the program receives certain signals (e....
Definition iloan.c:419
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:340
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:268
int f_i
Definition iloan.c:39
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:363
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:275
char * format_interest(float)
The format_interest function takes a floating-point number representing an interest rate and formats ...
Definition iloan.c:409
double accept_i()
The accept_i function prompts the user to enter the annual interest rate for the loan....
Definition iloan.c:227
int f_pv
Definition iloan.c:37
int is_numeric(char *)
The is_numeric function checks if a given string consists of numeric characters, including digits,...
Definition iloan.c:353
int f_pmt
Definition iloan.c:40
#define TRUE
Definition iloan.c:19
#define FALSE
Definition iloan.c:18
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:369
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:289
int main(int argc, char **argv)
Definition amort.c:30