Thread: Working with money?

  1. #1
    Registered User
    Join Date
    Mar 2012
    Posts
    33

    Working with money?

    I know you don't want to use floating point numbers when working with money because of the rounding errors, but instead use integers. I read somewhere that you should "multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. "

    Like this? If not can someone explain? Thank you.
    Code:
    #include <stdio.h>
    
    
    int main(){
        double dollars = 35;
        double cents = 19;
        
        dollars *= 100;
        dollars += 0.5;
        
        int intDollars = (int)dollars;
        intDollars /= 100;
        
        cents *= 100;
        cents += 0.5;
        
        int intCents = (int)cents;
        intCents /= 100;
        
        printf("$%d.%d\n", intDollars, intCents);
    
    
    }

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    What you read is incorrect. If you multiply by 100, then divide by 100, those two operations will cancel out.

    When you round and then multiply, you will end up with cents. Keep it this way until you need to display amounts or whatever, and then you divide to get a floating point number back.

  3. #3
    Ultraviolence Connoisseur
    Join Date
    Mar 2004
    Posts
    555
    Floating point is guaranteed 6 points of precision by the standard, your scenario requires only 2 points of precision; so why would you not want to use floats to represent monetary values?

  4. #4
    Registered User
    Join Date
    May 2003
    Posts
    1,619
    Quote Originally Posted by nonpuz View Post
    Floating point is guaranteed 6 points of precision by the standard, your scenario requires only 2 points of precision; so why would you not want to use floats to represent monetary values?
    Except that as you add and especially multiply these values, the cumulative error increases significantly.

    One particularly nasty example, albeit not in financials, was the death of 28 soldiers during the first Gulf War due to rounding error in a Patriot missile guidance system that failed to intercept a Scud. In spite of the fact that the computer's representation of 0.1 was off by only about one part in ten million, that inaccurate number was multiplied by a value of about 3.6 million, and in the end introduced about 500 meters of error in the final calculations of missile position.
    You ever try a pink golf ball, Wally? Why, the wind shear on a pink ball alone can take the head clean off a 90 pound midget at 300 yards.

  5. #5
    Ultraviolence Connoisseur
    Join Date
    Mar 2004
    Posts
    555
    You are correct, my mistake. Didn't think it fully through :P

  6. #6
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by nonpuz View Post
    Floating point is guaranteed 6 points of precision by the standard, your scenario requires only 2 points of precision; so why would you not want to use floats to represent monetary values?
    One fairly obvious reason is that floating point variables cannot exactly represent the decimal values 0.1 or 0.01 (or any negative power of 10, actually). There is always an error with such values, and the errors propagate with various basic math operations on floating point values (addition, subtraction, multiplication, division, raising to a power .....).
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  7. #7
    Registered User
    Join Date
    Mar 2012
    Posts
    33
    Alright, in case anyone else is trying to figure this out I think I got it. You need to work with money in cents. So $35.96 is 3596 in pennies, you need to store that number in an int. If you are gonna add or subtract some amount, you would do:
    Code:
    int cents = 3596;
    double amount = 0;
    
    
    puts("enter amount");
    scanf("%lf", &amount);
    
    
    amount *= 100;
    cents += (int)amount; //for addition
    cents -= (int)amount; //for subtraction
    In order to format the amount as money, cast cents to a double and divide by 100:
    Code:
    printf("%.2lf", (double)cents/100);
    If theres a better way to do it or if I'm wrong, someone can correct me.

  8. #8
    11DE784A SirPrattlepod's Avatar
    Join Date
    Aug 2013
    Posts
    485
    Assuming a 32-bit int, what happens if the person has more than $21474836.47 (i.e. just over 21 million dollars, so not unrealistic).

  9. #9
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by SirPrattlepod View Post
    Assuming a 32-bit int, what happens if the person has more than $21474836.47 (i.e. just over 21 million dollars, so not unrealistic).
    Same logic you would use if you wanted to represent any integral value, or a range of integral values that exceeds what is possible with your (biggest) integral type. Use some data structure that works with more than one integral value, and define various functions to support relevant operations (adding, subtracting, dividing, multiplying, printing, etc) on those data structures. That is essentially what big integer libraries do.

    With two 32 bit unsigned integral values, for example, it is possible to unambiguously represent all integral values from zero to 18446744073709551615 or, with a bit of additional work to manage signed arithmetic, from -9223372036854775807 to 9223372036854775807. With three or four 32-bit integral values, the supported range grows exponentially (albeit with additional memory usage to represent multiple values in code).

    Either way, you don't want to use floating point to represent large integral values. Floating point variables can't represent all integral values within their supported range.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  10. #10
    11DE784A SirPrattlepod's Avatar
    Join Date
    Aug 2013
    Posts
    485
    Quote Originally Posted by grumpy View Post
    Same logic you would use if you wanted to represent any integral value [...snip...]
    I know how I'd do it and my question was more targeted at the OP ;-) But that's ok you raise some good points. I don't think that unsigned 32-bit objects would be useful (money/balance can be negative). Even with a range of -9223372036854775807 to 9223372036854775807 I think that you're going to hit issues sooner rather than later in a real world situation. Even using 3 or 4 or 5 integer values you're still going to run into trouble.

    Of course floating point objects is not the way to go either and the OP already recognises this ;-)

  11. #11
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by SirPrattlepod View Post
    I don't think that unsigned 32-bit objects would be useful (money/balance can be negative).
    Sure. It's horses for courses and all that. It is not difficult to build a struct containing an unsigned 32 bit int and a char though. The char can represent sign (and more if needed).

    Quote Originally Posted by SirPrattlepod View Post
    Even with a range of -9223372036854775807 to 9223372036854775807 I think that you're going to hit issues sooner rather than later in a real world situation.
    If you mean that the values are finite, sure. There is no way to implement a variable with infinite range using a real computer. That requires an infinite address space, which is fairly difficult to implement in the real world.

    Quote Originally Posted by SirPrattlepod View Post
    Even using 3 or 4 or 5 integer values you're still going to run into trouble.
    Sure, but you missed my point that the possible range grows exponentially. The total number of atoms in the observable universe is estimated as about 10^80. That number can be represented using ten 32-bit unsigned ints.

    Yes, there are problems that require values that large. They exist, but are not common in the real world. Most of the time, when programmers claim they need to calculate a value that large, it is because they haven't through through the problem properly. Examples come up in these forums regularly and, sadly, the most common advice given is to reach for a large integer package. Amateurs!
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  12. #12
    Registered User
    Join Date
    Apr 2013
    Posts
    1,658
    Quote Originally Posted by nonpuz View Post
    Why would you not want to use floats to represent monetary values?
    In the real world, banks and some other financial institutions normally use binary coded decimal or the equivalent when performing math on monetary values, to avoid any issues with conversions involving floating point numbers. This type of math is supported in business languages such as Cobol (packed decimal), and many mainframes have binary coded decimal instructions that can operate on variable length strings of binary coded decimal digits.

  13. #13
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    Hmmm... Can't we just all agree to use long doubles and call it a day? What is it, long doubles go out to like 4000 decimals places, don't they? My bad, he should __float128 types.

  14. #14
    Registered User
    Join Date
    Mar 2010
    Posts
    583
    To the OP: the problem is that some innocent looking numbers in decimal can't be represented accurately in floating point, at all. Drama occurs when many operations make a large error out of a small error, but inaccuracy happens even in your example, because you read the value into a double.

    With some extra debugging printfs:
    Code:
    int cents = 3596;
    double amount = 0;
    
    puts("enter amount");
    scanf("%lf", &amount);
    
    printf("\ninput double: %lf", amount);
    amount *= 100;
    cents = amount;
    
    printf("\nCents amount: %d", cents);
    printf("\noutput double: %.2lf\n", (double)cents/100);
    Example output:
    Code:
    enter amount
    10.12
    
    input double: 10.120000
    Cents amount: 1011
    output double: 10.11
    If you change the format specifier for the input double to %.16lf you'll see 10.1199999999999992. So very nearly 10.12, but not quite! Casting to int discards the non-integer part, so 1011.999999999 is 1011.
    If the conversion was rounded rather than truncated, you'd get the right number. In your original post you said:
    I know you don't want to use floating point numbers when working with money because of the rounding errors, but instead use integers. I read somewhere that you should "multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies.
    What you'd heard there was someone suggesting a way to round to the nearest integer. You'd do that operation entirely on the double value. I think the multiply and divide by 100 are just to make sure the significant digits are out of the way (I think this is completely unnecessary). The add of 0.5 sets the integer part to the nearest integer. E.g. 1.1+0.5 = 1.6. 1.7+0.5=2.2. so the truncation to int gets the right value. This only works for positive numbers. Question 14.6

    I don't think you should add 0.5 in straight line code. Use a macro, or a small function, or just use the round() function from the C library. It's clearer -- there's no "magic constant" to trip people up.

    However, in this case rounding is a solution to a problem created by using doubles to read the input. I say "don't use doubles".

    Floats and double conveniently look a bit like money notation looks, but "10.12" is... a floating point number, 5 characters, 2 integers with a dot between them... C doesn't care too much. So for example you could do:
    Code:
       unsigned int dollars, cents;
    
       scanf("%u.%u", &dollars, &cents);
    From there, I agree with you that the easiest thing is to do all calculations in cents. Then convert back into two integers for printing.

    It is more complicated if you want to get back the flexibility you had with doubles. You could put in a negative number and it'd work, and you could put in just a single number to just be the dollars value. Signed numbers are easy, just be careful to get the conversion to/from cents right.
    Supporting a dollar-only input is probably tricky hassle with scanf. Better to read a string into a buffer and check the buffer for the different formats, then you can use sscanf to retrive the values.

    It probably sounds like my way is more long winded, more code, more hassle. If you're not going to do anything more with this bit of code then by all means use a rounded double. But if, for example, you decided to check the input was valid (e.g. 1.123 is not) then doubles will continue to need special care. I'd find it quite weird to see code to deal with the innacuracy of doubles in a program representing money in integers



    On the size of ints thing: it depends on what kind of money program it is. The OP didn't say. If it's meant to be a generic representation for a big bank, then yes, use something epic and worry about it a lot.
    If it's for adding up grocery lists $17 million is probably ok!
    Last edited by smokeyangel; 08-17-2013 at 05:54 AM.

  15. #15
    Registered User
    Join Date
    Jan 2011
    Posts
    144
    this is a very confusing topic indeed...
    I am reading C Programming: A modern approach by King - pg 133
    float precision is 6 digits whilst double precision is 15 digits.

    In the below code, output is:

    Code:
    float: 0.012345679104328
    double: 0.012345678900000
    Code:
    #include <stdio.h>
     
    int main()
    {
    	float foo = 0.0123456789f;
    	double ok= 0.0123456789;
    
    	printf("float: %.15f\n",foo);
    
    	printf("double: %.15lf\n",ok);
    
    	getchar();
    	return 0;
    }
    But if float is only precise to 6 digits, should it not just give random values after the 5th decimal place?

    Also, the largest value and smallest values that areable to be stored by type float is 3.40282x10^32 and 1.17549x10^-38 respectively. If a variable was assigned such a large(or small) number, it would not be accurate. So why are floats set up to hold such huge(tiny) values?
    Last edited by bos1234; 08-17-2013 at 08:13 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Wow money IS everything!!!
    By caroundw5h in forum A Brief History of Cprogramming.com
    Replies: 0
    Last Post: 03-27-2004, 03:27 PM
  2. any money anyone?
    By Fountain in forum A Brief History of Cprogramming.com
    Replies: 3
    Last Post: 08-14-2002, 07:58 PM
  3. the value of money
    By ygfperson in forum A Brief History of Cprogramming.com
    Replies: 3
    Last Post: 03-23-2002, 03:36 PM
  4. money
    By bob20 in forum C++ Programming
    Replies: 2
    Last Post: 12-14-2001, 12:41 PM