2
int y = 89;
int foo =  (y / 10.0 - y / 10) * 10;
int bar = (89 / 10.0 - 89 / 10) * 10;
cout << foo << ' ' << bar << '\n';

The above code is intended to get the last digit of an integer y, the strange thing is that foo is 8 and bar is 9, why this happens? What's the difference between the two versions of expressions?

Migo
  • 31
  • 1
  • 1
    This [test](https://godbolt.org/z/RSrboG) gives `9 9` Btw, why not do `y % 10` (a modulus operation) to get the last digit? – Ted Lyngmo Feb 27 '20 at 22:28
  • @TedLyngmo I use codeblocks compiler 17.12 – Migo Feb 27 '20 at 22:31
  • I'm pretty sure it supports modulus operations. Try `std::cout << y % 10 << '\n';` and you'll get the last digit. – Ted Lyngmo Feb 27 '20 at 22:32
  • @TedLyngmo I know that modulo 10 will give the intended answer, I just want to see if there's a difference between the two versions or not. – Migo Feb 27 '20 at 22:35
  • @Migo I can't see why it would be. `89/10.0 == 8.9` and `89/10 == 8`. `8.9 - 8 == 0.9` `0.9 * 10 == 9.0` and that's what's happening in both formulas. Are you saying that when you compile the test code I showed, you get `8 9` ? – Ted Lyngmo Feb 27 '20 at 22:40
  • @TedLyngmo Yes, but in Visual Studio I get `9 9`. – Migo Feb 27 '20 at 22:51
  • I'd look at the generated assembly code and maybe submit an error report. – Ted Lyngmo Feb 27 '20 at 22:51
  • I get 9, 9 compiling with g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 and also 9, 9 when using Code::Blocks 16.01 (but that also uses gnu gcc) – Nathan Wride Feb 27 '20 at 23:03
  • The only way I managed to reproduce your output is by messing up with the rounding modes, compiling without optimizations: https://godbolt.org/z/rQQQWt How have you set up your compilation? – Bob__ Feb 27 '20 at 23:48
  • 3
    @TarekDakhran: The fact you failed to reproduce the problem in your environment is not proof the problem does not exist. – Eric Postpischil Feb 28 '20 at 00:06
  • there are lots of duplicates: [double variable not equal to double expression?](https://stackoverflow.com/q/37626687/995714), [Apparently identical math expressions with different output](https://stackoverflow.com/q/33795031/995714), [Difference in floating point arithmetics between x86 and x64](https://stackoverflow.com/q/22710272/995714), [Difference in casting float to int, 32-bit C](https://stackoverflow.com/q/54881541/995714), [Nonintuitive result of the assignment of a double precision number to an int variable in C](https://stackoverflow.com/q/49012793/995714) – phuclv Feb 28 '20 at 02:31
  • Does this answer your question? [Apparently identical math expressions with different output](https://stackoverflow.com/questions/33795031/apparently-identical-math-expressions-with-different-output) – phuclv Feb 28 '20 at 02:32

1 Answers1

5

The C++ standard permits an implementation to evaluate floating-point expressions with more precision than is required by the nominal format. For example, float expressions may be evaluated as if they were double or more, and double expressions may be evaluated as if they were long double. This extra precision can cause differences in evaluation, especially where discontinuous functions (such as conversion to int) are used.

For example, given y = 89, y / 10.0 - y / 10 would be .9 in real-number arithmetic, but it is 0.9000000000000003552713678800500929355621337890625 in double (IEEE-754 binary64) arithmetic and 0.89999999999999999965305530480463858111761510372161865234375 in long double (Intel’s 80-bit format) arithmetic, and then multiplying by 10 and converting to int produces 9 or 8, respectively.

With optimization disabled, the compiler may be evaluating the expression with y at run-time and the expression with 89 during compilation, and it may be using different precisions for them. With optimization, the compiler is likely to recognize y is effectively a constant 89 in the first expression and evaluate both expressions during compilation, use the same method for both.

The C++ standard requires that cast and assignment operations convert to the nominal type, so one test to see if this is happening is to insert casts:

int foo = (double) ((double) (y / 10.0) - y / 10) * 10;
int bar = (double) ((double) (89 / 10.0) - 89 / 10) * 10;

If this results in foo and bar being identical, that supports the hypothesis. Your compiler may have switches to control how floating-point expressions are evaluated.

Another test is to include <cfloat> and print the value of FLT_EVAL_METHOD. If it is 0, the implementation claims to evaluate floating-point operations in their nominal type, and this behavior should not occur. If it is 1 or 2, the implementation claims to use double or long double to evaluate double expressions, respectively, and this behavior should again not occur, since both expressions will be evaluated the same way. If it is −1, the implementation is not making these claims, and the behavior may occur.

To get the last digit of a non-negative integer, use y % 10.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312