4

Hey everybody I am working on a program in c that tells you the least number of coins needed for any given amount of money. I have a program written that works for for every amount I have tested except for $4.20.

Here is my code:

#include <cs50.h>
#include <stdio.h>
#include <math.h>

int main(void)
{
    float f;
    int n, x, y, z, q, s, d, t;

    do {
        printf("How much change do you need?\n");
        f = GetFloat();
    } while(f <= 0);

    {
        n = (f * 100);
    }

    q = (n / 25);
    x = (n % 25);
    y = (x / 10);
    z = (x % 10);
    s = (z / 5);
    d = (z % 5);
    t = (q + y + s + d);

    {
        printf("%d\n" ,t);
    }
}

The strange thing is when I input 4.20 the output is 22 instead of 18 (16 quarters and 2 dimes). I did some sleuthing and found that the problem is with my variable x. When I input 4.2, x gives me 19 and not 20 like it should. I tried other cases that I thought should have produced the same problem like 5.2 and 1.2 but it worked correctly in those cases. It might be a rounding issue but I would think that same error would also happen with those similar values.

Does anyone have an idea about why this might be happening?

PS I am fairly new to coding and I haven't gotten much formal instruction so I also welcome tips on better indentation and formatting if you see anything obvious.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 5
    Rounding error, n is probably 419. Use `n = round(f*100);` – Ctx Mar 16 '16 at 19:56
  • It's probably because your program is straight edge. – DigitalNinja Mar 16 '16 at 19:59
  • 3
    `float f;` is already wrong. Never use floating point if you need exact values! With a little effort you will find this problem asked here multiple times already. – too honest for this site Mar 16 '16 at 20:02
  • 4
    Variable names can be (almost) any length. Use this wisely, by giving them descriptive names! – Daniel Jour Mar 16 '16 at 20:02
  • @DanielJour: Actually it's at least 63 characters (see the standard, 5.2.4.1). – too honest for this site Mar 16 '16 at 20:05
  • @Olaf I know. Long enough to be descriptive. It seems to be a common failure of tutorials as well as training courses to just use one-letter variable names without any thought. – Daniel Jour Mar 16 '16 at 20:07
  • 3
    @Olaf: There is nothing wrong with `float` when you're required to get a `float` from the user (I assume that's what the `GetFloat` function is returning). The OP is doing the right thing by converting to `int` early on, they just forgot to account for IEEE float imprecision and didn't round prior to conversion to `int` cents. – ShadowRanger Mar 16 '16 at 20:07
  • 2
    Yeah, one can use `longDescriptiveName1, longDescriptiveName2, longDescriptiveName3`... :) – Eugene Sh. Mar 16 '16 at 20:08
  • 1
    @ShadowRanger: No. It already starts with input values not being exactly representable in a floating point variable. There are other ways, e.g. simulate inputting a float by two integers. For currency input you don't need scientific notiation input. – too honest for this site Mar 16 '16 at 20:10
  • @EugeneSh.: I actually prefer `__local_variable_nesting_level_42_i`, `__local_variable_nesting_level_23_j`. But everyone as she wishes. – too honest for this site Mar 16 '16 at 20:12
  • 1
    NEVER, NEVER, NEVER, under any circumstances, use *float* or *double* for money. – Lee Daniel Crocker Mar 16 '16 at 20:50

2 Answers2

3

IEEE 754 floating point is often slightly imprecise, and casting will truncate, not round. What's likely happening is that 4.20 * 100 evaluates to 419.999999999999994 (exact number is immaterial, point is, it's not quite 420), and the conversion to int drops the decimal portion, producing 419.

The simple approach is to just do:

n = f * 100 + 0.5;

or you can use a proper function:

n = round(f * 100);

If the number is "almost" exact, either one will be fine, you'd only get discrepancies when someone passed non-integer cents ("4.195" or the like), and if you're using float for monetary values, you've already accepted precision issues in the margins; if you want exact numbers, you'd use the decimal formats that have fixed precision for decimal values, and are intended for financial calculations.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 1
    It is not a matter of imprecision, but of the number base. The user is using base 10, but the code is using base 2 which does not have an exact equivalence. – ravenspoint Mar 16 '16 at 20:18
  • 2
    @ravenspoint What you describe is the _reason_ for the imprecision. – Ctx Mar 16 '16 at 20:21
  • 1
    Better to use `round(f * 100);` `f * 100 + 0.5;` fails for many negative numbers, `f` just less than 0.5 and many `f` whose ULP is 0.5 or 0.25. – chux - Reinstate Monica Mar 16 '16 at 20:27
  • @Ctx It is important to understand the reason. If you do not know, you might think that changing from floats to doubles would help. It wouldn't. Although doubles are more precise, they do not have exact equivalents of base 10. – ravenspoint Mar 16 '16 at 20:45
  • 1
    @ravenspoint Yes, it was fine that you explicitely stated the reason here, it should probably even be appended to the answer above. I just didn't agree not to call it "imprecision" – Ctx Mar 16 '16 at 20:47
  • `round` should always be used. Another problem with `0.5` is failure for large numbers where the distance between adjacent floats is getting towards the magnitude of 1 – M.M Mar 16 '16 at 21:13
-2

Try this: Provides up to 2 digit precision.

//float f
double f

f *= 1000;
f = floor(f);  /* optional */
f /= 10;
f = floor(f);  /* optional */
n = f;
Ravi
  • 88
  • 5
  • @patrick-leppink-shands This is to provide 2 digit precision. – Ravi Mar 16 '16 at 20:52
  • @patrick-leppink-shands, As far as the coding style is concerned use comments to explain specific parts of the code, make use of a bit more descriptive names for variables, proper indentation, use blank lines to be easy on the eyes etc. – Ravi Mar 16 '16 at 20:56
  • 1
    This doesn't avoid the problem – M.M Mar 16 '16 at 21:11
  • Adding an explanation of how this code answers the question will improve your answer for future visitors (this answer was flagged as low-quality). – JAL Mar 16 '16 at 23:11
  • @JAL, it is a tested code. It essentially works by rounding up the precision for two digits, before assignment. Please try it yourself. I would highly recommend not to flag the answers without convincing yourself first. – Ravi Mar 16 '16 at 23:57
  • @M.M, please clarify, how doesn't it solve/avoid the problem? – Ravi Mar 16 '16 at 23:58
  • @Ravi I didn't flag the answer, it came up as I was reviewing the low quality posts queue. – JAL Mar 17 '16 at 00:05
  • @JAL, thanks for clarifying it. I am a new user to stack exchange/overflow and just getting acquainted with the nuances of using the features to post answers :-) – Ravi Mar 17 '16 at 00:07
  • If the real value is 4.1999999 for example (as shown in ShadowRanger's answer), then you do 4199.9999/10 which is still 419.999. You are basically rolling a dice and hoping it lands on 420 instead of 419 – M.M Mar 17 '16 at 00:16
  • @M.M, I tested the code (didn't roll a dice) like I mentioned above and see my comment about 2 digit precision also. Did you try it in the first place before posting a comment? – Ravi Mar 17 '16 at 19:53
  • @M.M, How much change do you need? 4.1999999 Value entered : 4.200000 Quarters needed : 16 Dimes needed : 2 5 Cents needed : 0 Cents needed : 0 Total coins needed : 18 Program ended with exit code: 0 – Ravi Mar 17 '16 at 20:08
  • @M.M, How much change do you need? 4.20 Value entered : 4.200000 Quarters needed : 16 Dimes needed : 2 5 Cents needed : 0 Cents needed : 0 Total coins needed : 18 Program ended with exit code: 0 – Ravi Mar 17 '16 at 20:08
  • @M.M, more How much change do you need? 4.1999999 Value entered : 4.200000 f *= 1000 : 4200.000000 f /= 10 : 420.000000 n = f : 420 – Ravi Mar 17 '16 at 20:18
  • @M.M, How much change do you need? 4.20 Value entered : 4.200000 f *= 1000 : 4200.000000 f /= 10 : 420.000000 n = f : 420 – Ravi Mar 17 '16 at 20:19
  • @M,M, by changing float f to double f, the result is more accurate. How much change do you need? 4.1999999 Value entered : 4.200000 f *= 1000 : 4199.999900 f /= 10 : 419.999990 n = f : 419 Quarters needed : 16 Dimes needed : 1 5 Cents needed : 1 Cents needed : 4 Total coins needed : 22 How much change do you need? 4.20 Value entered : 4.200000 f *= 1000 : 4200.000000 f /= 10 : 420.000000 n = f : 420 Quarters needed : 16 Dimes needed : 2 5 Cents needed : 0 Cents needed : 0 Total coins needed : 18 – Ravi Mar 17 '16 at 20:28
  • @Ravi other people may get different results to you – M.M Mar 17 '16 at 20:55
  • @M.M then, there are standards, then portability of code in C/C++ and math library and it's implementation, representation of floating/double in the processor. So, I recommended a solution based on the test results. One may have to experiment with their system to see how it works for them. Using round(), ceil(), floor() etc., which could resulting in entirely different results. – Ravi Mar 18 '16 at 02:59
  • 1
    @Ravi Trying things "to see how it works for you" is exactly what should be avoided. Standards are there so that all implementations behave the same way (at least to an extent), so solutions work on all systems and no trial/error is needed. For this case, the real solution is to avoid `float` or `double` data types, and if this is completely impossible then `round()` is the best one can hope for. Your solution uses `floor()` which leads exactly into the kind of problem described in the question. I don't intend to be harsher than necessary but I'm adding my downvote. – Jojonete Mar 18 '16 at 12:04
  • @Jojonete, I think you mistook the whole point I was making. Standards are there make the code portable and so algorithms. Rounding isn't the best option in this case. What I tried to show is that despite of using all the standards, you will still see the variations. I would highly recommend folks who comment should make an effort to solve the problems and come with ideas and not just be happy voters. Just think of how would you solve the problem without math library functions? – Ravi Mar 18 '16 at 14:54
  • @Jojonete, I would go one step further. Algorithms should work irrespective of programming languages. It is the internal representation and division and multiplication operations that makes the difference. For further background consult computer numerical analysis material and see how the imprecision accumulates over. – Ravi Mar 18 '16 at 15:46