Here is an elaborate program with the following features:
- Accepts income of up to ca. 1.8 * 1013 units (Rupees, Dollars etc).
- Computes cent-exact.
- Accepts data from the command line or stdin.
- Prompts when stdin is a terminal.
- Handles input errors.
- Even on a terminal one can simply paste space multiple separated numbers,no need to have one per line (6th input prompt below).
Maybe some things apart from the actual algorithm are useful here:
- Use a return type which can communicate errors (
struct ParseResult
). - Use the return value of
scanf
and other input functions to detect input errors, and distinguish it from end-of-file. - Move similar-looking code into parametrized functions instead of duplicating it (
MakeParseResult()
makesParseIncomeInHundredthsFromString()
andParseIncomeInHundredthsFromStream()
both small). - Some algorithms are well-suited for recursion, like this one (
computeTaxes()
). - Separate input from processing (
computeTaxes()
knows nothing about I/O). - Like in Allan's answer, do not hard-wire data in the code (the
taxSteps
array could be read from somewhere). - Pay attention to value ranges (32 bits are not enough for big incomes, let alone in cent or Rupees).
- Use the proper conversion specifiers in
printf()
andscanf()
, here:PRIu64
andSCNu64
. It's somewhat cumbersome but necessary for portability. Different implementations have different native data types for theuint.._t
typedefs, and thus need different conversion specifiers (what's%ul
on Linux is%ull
on Windows, for example). - Even though it is not standard C, some common Posix functions like
isatty()
are available in non-Posix implementations as well. They are useful.
Sample sessions:
$ gcc -Wall -o income-tax income-tax.c && ./income-tax.exeTax brackets:0.00 .. 1000.00: 2.00%1000.00 .. 3000.00: 7.00%Above: 11.00%Please input income values in a format like '12345.99 ...'1000.00Taxes for 1000.00: 20.00Please input income values in a format like '12345.99 ...'3000.00Taxes for 3000.00: 160.00Please input income values in a format like '12345.99 ...'10000.00Taxes for 10000.00: 930.00Please input income values in a format like '12345.99 ...'10000.10Taxes for 10000.10: 930.01Please input income values in a format like '12345.99 ...'1234.56 7890.89 121212121212.12Taxes for 1234.56: 36.41Please input income values in a format like '12345.99 ...'Taxes for 7890.89: 697.99Please input income values in a format like '12345.99 ...'Taxes for 121212121212.12: 13333333163.33Please input income values in a format like '12345.99 ...'$
From command line and per pipe:
$ ./income-tax.exe 1500.00 4500.25 1234567890123.00Taxes for 1500.00: 55.00Taxes for 4500.25: 325.02Taxes for 1234567890123.00: 135802467743.53$ echo "1500.00 4500.25 1234567890123.00" | ./income-tax.exeTaxes for 1500.00: 55.00Taxes for 4500.25: 325.02Taxes for 1234567890123.00: 135802467743.53
//*************************************************// A tax computing program for a multi-step tax bracket.// The brackets are specified in the taxSteps array.// The program is currency agnostic but assumes a // "unit" and "hundredths" (Dollar/Cent, Euro/Cent, Pound/Penny).// It stores money values in those "hundredths" (e.g. Cents) // using 64 bit integers. (32 bit would not quite be enough// for the highest earners; Elon Musk payed $11bn in 2021.)// The tax rate is likewise stored in 1/100s of a percent// to facilitate non-integer tax rates.// All arithmetics are integer; resulting taxes are rounded down.//// The program accepts input as commandline arguments // ("income-tax 1234.56 777.88") or from standard input// ("echo 1234.56 777.88 | income-tax").// Iff standard input is a terminal it issues a prompt.#include <stdio.h>#include <stdint.h> // for 64 bit type#include <assert.h>#include <limits.h> // uint64_max#include <inttypes.h>#include <stdlib.h> // exit#ifdef _MSC_VER // microsoft compiler, as opposed to *nix environment# define _CRT_SECURE_NO_WARNINGS // silence MSVC# pragma warning(disable : 4996) // MSVC prefers _isatty# include<io.h>#else# include <unistd.h>#endif// Convenience function because of the unwieldy format strings.// Prints "a string like "123.45" from 12345.void printHundredths(uint64_t Hundredths){ printf("%" PRIu64 ".%02" PRIu64, Hundredths / 100, Hundredths % 100);}/// A single tax step: An interval in centi-money units with a rate in "centipercent".struct TaxStep{ uint64_t StepSizeInHundredths; unsigned int TaxRateInHundredthPercent;};uint64_t computeTaxesInHundredthsForStep(uint64_t incomeInHundredths, struct TaxStep taxStep){ uint64_t taxedInThisStep = incomeInHundredths < taxStep.StepSizeInHundredths ? incomeInHundredths : taxStep.StepSizeInHundredths; // Such a high income would overflow in the expression below and compute // a tax amount that's much too low. The threshold, for 100% tax, is // > 1.8E15 Hundredths, or 1.8E13 whatever (dollars, rupees). In rupees // we get close to a realistic income (one rupee ~ 1 cent), 1.8E12, // 1.8 trillion dollars. If, say, Elon Musk liquidates its entire // stock at the zenith of his wealth, in Rupees or Lira, we could come close. // Let's say that's OK for now. if ( taxStep.TaxRateInHundredthPercent != 0&& taxedInThisStep > UINT64_MAX / taxStep.TaxRateInHundredthPercent) { fprintf(stderr, "Income "); printHundredths(incomeInHundredths); fprintf(stderr, " exceeds value range.\nMax permissible : "); printHundredths(UINT64_MAX / taxStep.TaxRateInHundredthPercent); exit(2); } // taxableMoney * HundredthsPercent is precise // (and won't overflow, we asserted that above). // The division is exact to the cent, rounded down (we are a nice tax office). // The rate is in "centipercent", so we must then divide by 100*100. // With percent: "money*11/100" with "centipercent": "money*1100/10000". return (taxedInThisStep * taxStep.TaxRateInHundredthPercent) / 10000;}/// <summary>/// Step through the tax bracket and apply the tax rate to each/// step. The steps are interval sizes, not absolute values./// </summary>/// <param name="incomeInHundredth">The income in cents or whatever</param>/// <param name="taxSteps">The first element in an array of tax steps which have/// an interval size and a rate for that interval.</param>/// <returns></returns>uint64_t computeTaxes(uint64_t incomeInHundredth, struct TaxStep* taxSteps){ // The function simply calls itself with the remaining money // and the remaining tax brackets. Because the last interval is // UINT_MAX, incomeInHundredth <= taxSteps->StepSizeInHundredths // always triggers for the last interval, stopping the recursion. return computeTaxesInHundredthsForStep(incomeInHundredth, *taxSteps)+ ( incomeInHundredth <= taxSteps->StepSizeInHundredths ? 0 : computeTaxes(incomeInHundredth - taxSteps->StepSizeInHundredths, taxSteps+1) );}// A convenience function.void errExit(const char* msg){ fprintf(stderr, msg); exit(1); // indicates error}/// <summary>/// Communicates the result of a parsing attempt. EOI is "end of information"./// </summary>enum ParseDiagE { ParseDiag_ERR, ParseDiag_EOI, ParseDiag_OK };/// <summary>/// A 64 return value which also carries an indicator whether/// the value could be set properly from the input./// </summary>struct ParseResult{ uint64_t parsedValue; enum ParseDiagE parseDiag;};/// <summary>/// Factored out the common logic of the two parse methods below./// Sets the ret.parseDiag member appropriately, depending on the scanf diagnostic/// return value (which indicates the number of parsed items and should be 2)./// If the diag value is OK, combines the two numbers before and after the dot/// to set ret.parsedValue,/// </summary>/// <returns>the result structure</returns>struct ParseResult MakeParseResult(uint64_t incomeInInUnits, uint64_t IncomeFractionInHundredths, int scanfDiag){ struct ParseResult ret = { 0, ParseDiag_OK }; switch (scanfDiag) { case EOF: // typically OK: End of string, or end of file/pipe closed. ret.parseDiag = ParseDiag_EOI; break; case 0: case 1: // bad input: scanf should have read 2. ret.parseDiag = ParseDiag_ERR; break; } if (IncomeFractionInHundredths > 99) { // Could be parsed, but too many "cents", e.g. 1.234 ret.parseDiag = ParseDiag_ERR; } if (ret.parseDiag == ParseDiag_OK) { // only if everything is OK, actually compute anything. ret.parsedValue = incomeInInUnits * 100 + IncomeFractionInHundredths; } return ret;}/// <summary>/// Expects a number in the English float format [0-9]+\.[0-9]+/// </summary>/// <returns></returns>struct ParseResult ParseIncomeInHundredthsFromString(const char* input){ uint64_t incomeInInUnits = 0, IncomeFractionInHundredths = 0; int diag = sscanf(input, "%" SCNu64 ".%" SCNu64, &incomeInInUnits, &IncomeFractionInHundredths); return MakeParseResult(incomeInInUnits, IncomeFractionInHundredths, diag);}/// <summary>/// Expects a number in the English float format [0-9]+\.[0-9]+/// Output a prompt only if asked (do not spam sdout for batch runs)./// </summary>/// <returns></returns>struct ParseResult ParseIncomeInHundredthsFromStream(FILE *stream, int prompt){ if (prompt) { printf("Please input income values in a format like '12345.99 ...'\n"); } uint64_t incomeInInUnits = 0, IncomeFractionInHundredths = 0; int diag = fscanf(stream, "%" SCNu64 ".%" SCNu64, &incomeInInUnits, &IncomeFractionInHundredths); return MakeParseResult(incomeInInUnits, IncomeFractionInHundredths, diag);}void PrintTaxBrackets(struct TaxStep *steps){ uint64_t overallIncomeInHundredths = 0; printf("Tax brackets:\n"); while(1) { if (steps->StepSizeInHundredths == UINT64_MAX) { printf("Above: "); printHundredths(steps->TaxRateInHundredthPercent); printf("%%\n"); break; } else { printHundredths(overallIncomeInHundredths); printf(" .. "); printHundredths(overallIncomeInHundredths += steps->StepSizeInHundredths); printf(": "); printHundredths(steps->TaxRateInHundredthPercent); printf("%%\n");++steps; } }}int main(int argc, char** argv){ // The specification of the tax brackets: A succession of intervals // with their respective tax rate, in "centipercent" (1120 would be 11.20%). // The last step **must** be of size UINT64_MAX (i.e., the remaining income), // that's how the program recognizes it's the last step struct TaxStep taxSteps[] = { {1000 * 100, 2 * 100}, {2000 * 100, 7 * 100}, {UINT64_MAX, 11 * 100 } }; // If we have command line arguments we should process those. int ShouldProcessArgv = argc > 1; // If stdin is a terminal int inpIsFromTerminal = isatty(fileno(stdin)); // spam stdout only for interactive sessions. if (!ShouldProcessArgv && inpIsFromTerminal) { PrintTaxBrackets(taxSteps); } /// Command line argument index. argv[0] is the program name, /// so we start with index 1 for the first actual argument. int argi = 1; // loop through text command line arguments or textual numbers // from stdin. do { // Iff there are no command line arguments, we check standard input. // Apart from the input source, the processing is the same. struct ParseResult res = ShouldProcessArgv ? ParseIncomeInHundredthsFromString(argv[argi]) : ParseIncomeInHundredthsFromStream(stdin, inpIsFromTerminal); if (res.parseDiag == ParseDiag_ERR) { errExit("Input format error\n"); } else if (res.parseDiag == ParseDiag_EOI) { break; } printf("Taxes for "); printHundredths(res.parsedValue); printf(": "); printHundredths(computeTaxes(res.parsedValue, taxSteps)); printf("\n"); } while (ShouldProcessArgv ? ++argi < argc : 1); return 0;}