1

I've looked at this question, and it doesn't appear to be the same situation.

I have a function in the check register program I'm writing that reads in a dollar amount from the user, validates it wasn't entered with fractional cents, and checks with the user that it was entered correctly. What I'm getting, though, is that after multiplying the float value that was entered by 100 and truncating with (int), the value changes. For instance, if I enter 1118.58, I can (via debug printf statements) verify that 1118.58 was scanned and assigned to amt correctly, and my while conditional (int)100 * amt == 100 * amt is correctly testing as TRUE, but after conversion to integer, the integer value is stored as 111857. More perplexing, it doesn't happen with every value (though I'm just getting going well on testing, this is the first entry I've seen change this way).

I'm using gcc on Kubuntu 14.04 64-bit, with C99 standard setting in my compile command. Here's the function that's giving the problem (presuming you don't want/need to see 1300 lines of the complete program). Note: I have Boolean values and operators defined in a header file for my convenience -- I've recently discovered there's a standard way to do that, but haven't converted my header file yet.

int get_amt (void)
{
  float amt;
  int scanned, i_amt;
  int success = FALSE;
  char yn = '\0';

  while (NOT success)
  {
    scanned = scanf("%f%*c", &amt);

    /* validate and verify amount */
    if (scanned >= 1 AND (int)100*amt == 100*amt AND amt <= 100000)
    {
      i_amt = (int)(100 * amt);
      printf("\n amt = %4.2f i_amt = %i", amt, i_amt);
      printf("\nYou entered $%i.%.2i -- Correct (Y/n)?", i_amt/100, i_amt%100);
      scanf("%c%*c", &yn);
      if (yn == '\0' OR yn == 'Y' OR yn == 'y')
        success = TRUE;
    }
  }
  return (i_amt);
}

And here's an output sample with the debug print statement:

Enter deposit amount (dollars and cents): $ 1118.58                             

 amt = 1118.58 i_amt = 111857                                                   
You entered $1118.57 -- Correct (Y/n)?                                          

Why is this happening, and how do I fix it?

After discussion in the comments below, I've converted the function to use integers, so there's no question of float precision (I hadn't really thought about just how little precision a standard float has on a 64-bit OS and compiler). Here's the updated version, which can't give this failure because there are no floats:

int get_amt (void)
{
  int scanned, amt1, amt2;
  int success = FALSE;
  char yn = '\0';

  while (NOT success)
  {
    scanned = scanf("%i.%i%*c", &amt1, &amt2);

    /* validate and verify amount */
    if (scanned >= 1)
    {
      printf("\nYou entered $%i.%.2i -- Correct (Y/n)?", amt1, amt2);
      scanf("%c%*c", &yn);
      if (yn == '\0' OR yn == 'Y' OR yn == 'y')
    success = TRUE;
    }
  }
  return (100*amt1 + amt2);
}
Community
  • 1
  • 1
Zeiss Ikon
  • 481
  • 4
  • 13
  • 2
    Hint: precision: your float value is actually like 1118.57999999999 – Mitch Wheat Jan 04 '15 at 02:33
  • possible duplicate of [Float to Int conversion](http://stackoverflow.com/questions/20388067/float-to-int-conversion) – Mitch Wheat Jan 04 '15 at 02:35
  • This is entered directly with two digits after the decimal; it's not a calculated value I'm converting. Where is the other .0000000001 going? If that's the problem, the obvious solution is to convert this function to input in integer, like `scanf ("%i.%i%*c", amt1, amt2)` -- but why is it a problem? – Zeiss Ikon Jan 04 '15 at 02:37
  • 1
    It doesn't matter that you entered the number directly. Internally, when `1118.58` is converted to binary, it can lose precision unless `0.58` were able to be described exactly as a finite sum of negative powers of `2` that fit within the IEEE float number of bits available. The binary system, with a fixed number of binary digits, can't exactly represent all possible, finite decimal expansions. – lurker Jan 04 '15 at 02:38
  • 1
    `(int)100*1118.58f` is also not an int, it's a float, which is why the check in the `if` passes. – lared Jan 04 '15 at 02:43
  • @lared `(int) 100 * 1118.58` gets promoted back to float for the comparison, but it seems it should be comparing 111857.0 to 111858.0 if the actual convert-and-store is losing a bit's worth... – Zeiss Ikon Jan 04 '15 at 02:55
  • `(int) 100 * 1118.58f` is exactly the same as `100 * 1118.58f`. It's likely optimized away the moment you compile it. You lose precision during multiplication since the mantissas have to be aligned. The error of single-precision IEEE-754 representation shouldn't be higher than 1e-7. – lared Jan 04 '15 at 03:05

1 Answers1

5

As @MitchWheat has already alluded to in a comment above, it is likely that when you print out the float, it rounds up, whereas when you calculate the int, it simply truncates. Get around this by using the round function. I.e., i_amt = round(100 * amt);, which will round the number in the same manner as the float is rounded.

wolfPack88
  • 4,163
  • 4
  • 32
  • 47
  • You might also consider using a `double` for better precision or obtaining the value by a different means if you're planning on having more than 15 significant figures, which seems plausible if you're dealing with money. – Dtor Jan 04 '15 at 02:38
  • @ZeissIkon: How exactly would this problem be in the past? Whatever number you put in, the computer still has to convert it to binary, and `0.58` simply cannot be converted exactly in binary, the same way `1/3` cannot be represented exactly in decimal. – wolfPack88 Jan 04 '15 at 02:41
  • @Dtor "more than 15 significant figures, which seems plausible if you're dealing with money." -- not in any personal checkbook I've ever seen. ;) Clearly, I need to change this to input in integer (fortunately, I can fix this *once*, since I put all the amount entries for the entire program through this one function). – Zeiss Ikon Jan 04 '15 at 02:41
  • @Zeiss it's not the Pentium math problem, it's how floats are defined. Floats only allow around 6 significant digits of precision, which in your case you lose the 6th due to how you converted it (not using `round`). Double only allows for 15 significant digits. The Pentium problem was something completely different, it was a mismatch in what it said it did versus what it accidentally did. Floats and doubles are well defined. – Dtor Jan 04 '15 at 02:42
  • @ZeissIkon Yaaaa....I had my number of figures confused. 15 sig digits is 13 figures dropping 2 to the change. Probably not a normal number money wise. – Dtor Jan 04 '15 at 02:46
  • Sorry, the 1st-gen Pentium comment was intended as a joke... I was taken by surprise on this when it came up in testing, but I see now it's the reason balances in bank software are stored as integer cents (which I do everywhere else in this program, just took the amounts in float because it was easier). – Zeiss Ikon Jan 04 '15 at 02:47
  • @Dtor: I think you meant to say "if you're planning on having more than *seven* significant digits", which is the accuracy of a float (and easily exceeded in typical banking applications). – rici Jan 04 '15 at 02:47
  • @rici his example was only 6 significant digits. I think floats give UP to 7 sig digits, but not in all cases. In worst cases, it's 6 digits, and significant digits, not just digits. – Dtor Jan 04 '15 at 02:49
  • Number of sig-figs matters not one whit if the part behind the decimal won't stay where you put it. Integer cents it will be... – Zeiss Ikon Jan 04 '15 at 02:49
  • 1
    @ZeissIkon Actually, re-thinking the 15 digits again, maybe `double` isn't good enough -- if you're prompting the user to put in the US budget deficit! Haha... – Dtor Jan 04 '15 at 02:49
  • @Dtor: a float has 24 significant bits, which means that the smallest unrepresentable positive integer is 16777217 (2^24+1), or in other words that a float can precisely represent any seven-digit integer. Beyond that, no. – rici Jan 04 '15 at 02:54
  • @Dtor Projecting [U.S. debt](http://www.treasurydirect.gov/govt/reports/pd/histdebt/histdebt.htm) to the penny using typical `double` will lose precision Oct 23, 2035. To the dollar, we are good til 2094. – chux - Reinstate Monica Jan 04 '15 at 04:43
  • @wolfPack88 Detail: `printf()` rounds using the current rounding mode which is typically "round-to nearest", not necessarily "up". – chux - Reinstate Monica Jan 04 '15 at 04:46
  • Agree with @Dtor. Better to use `i_amt = round(100.0 * amt);` to force a `double` multiplication than `100 *amt` which may only use `float`. – chux - Reinstate Monica Jan 04 '15 at 04:49
  • @wolfPack88 "... round(100 * amt);, which will round the number in the same manner as the float is rounded." is **not** correct. `float` are rounded in `printf()` per the current rounding mode. `round()` uses round-to-nearest regardless of rounding mode. – chux - Reinstate Monica Jan 04 '15 at 05:05
  • @chux: I never said anything about it rounding up always, `ceil` would do that. I said it would round the number in the same manner as the `float` is rounded. Technically, yes, the `printf()` command rounds per current rounding mode, but the default on every system I've ever come across is round-to-nearest. If you know enough about `float`s and rounding to change the rounding mode, you know enough to not need to ask this question, so I left out that info as miscellaneous info. I can add it back in if you'd like. – wolfPack88 Jan 04 '15 at 14:18