Bug 210529 - 64-bit signed / unsigned division gives the wrong result on RHEL4 64-bit with gcc 3.4.6
64-bit signed / unsigned division gives the wrong result on RHEL4 64-bit with...
Status: CLOSED NOTABUG
Product: Red Hat Enterprise Linux 4
Classification: Red Hat
Component: gcc3 (Show other bugs)
4.0
x86_64 Linux
medium Severity medium
: ---
: ---
Assigned To: Jakub Jelinek
:
Depends On:
Blocks:
  Show dependency treegraph
 
Reported: 2006-10-12 14:56 EDT by Chris Palmer
Modified: 2007-11-30 17:07 EST (History)
1 user (show)

See Also:
Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of:
Environment:
Last Closed: 2006-10-12 17:00:48 EDT
Type: ---
Regression: ---
Mount Type: ---
Documentation: ---
CRM:
Verified Versions:
Category: ---
oVirt Team: ---
RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: ---


Attachments (Terms of Use)
test program for divide bug (308 bytes, text/plain)
2006-10-12 14:56 EDT, Chris Palmer
no flags Details

  None (edit)
Description Chris Palmer 2006-10-12 14:56:57 EDT
Description of problem:
On RHEL4 64 bit with gcc 3.4.6, 64 bit signed / unsigned division gives the
wrong result (AMD64). The compiler is incorrectly generating an unsigned divide
instruction (divq) instead of a signed divide (idivq).

Version-Release number of selected component (if applicable):
gcc-3.4.6-3

How reproducible:
Compile & run attached program.  Observe incorrect results.

Steps to Reproduce:
1. $ gcc divide_bug.cpp -lstdc++
2. $ ./a.out
  
Actual results:
See output of test program (large unsigned value returned on our system).

Expected results:
Expecting a correctly computed result (-40, in this case).

Additional info:
Sample test program (divide_bug.cpp) attached.
Comment 1 Chris Palmer 2006-10-12 14:56:57 EDT
Created attachment 138353 [details]
test program for divide bug
Comment 2 Jakub Jelinek 2006-10-12 17:00:48 EDT
Please read the relevant portions of the standards (ISO C++98 doesn't include
long long type, it is just a G++ extension, so you need to read both ISO C99
(which has it) and ISO C++98):

ISO C99, 6.3.1.8:
...
Otherwise, the integer promotions are performed on both operands. Then the
following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is
needed.  Otherwise, if both operands have signed integer types or both have
unsigned integer types, the operand with the type of lesser integer conversion
rank is converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or
equal to the rank of the type of the other operand, then the operand
with signed integer type is converted to the type of the operand with
unsigned integer type.
Otherwise, if the type of the operand with signed integer type can represent
all of the values of the type of the operand with unsigned integer type, then
the operand with unsigned integer type is converted to the type of the
operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type
corresponding to the type of the operand with signed integer type.

ISO C++98, [expr]/9:
Many binary operators that expect operands of arithmetic or enumeration type
cause conversions and yield result types in a similar way. The purpose is to
yield a common type, which is also the type of the result.
This pattern is called the usual arithmetic conversions, which are defined
as follows:
- If either operand is of type long double, the other shall be converted
  to long double.
- Otherwise, if either operand is double, the other shall be converted to
  double.
- Otherwise, if either operand is float, the other shall be converted to
  float.
- Otherwise, the integral promotions (4.5) shall be performed on both
  operands.
- Then, if either operand is unsigned long the other shall be converted to
  unsigned long.
- Otherwise, if one operand is a long int and the other unsigned int, then
  if a long int can represent all the values of an unsigned int, the
  unsigned int shall be converted to a long int;
  otherwise both operands shall be converted to unsigned long int.
- Otherwise, if either operand is long, the other shall be converted to
  long.
- Otherwise, if either operand is unsigned, the other shall be converted
  to unsigned.
[Note: otherwise, the only remaining case is that both operands are int]

So, in your case, the first operand of / needs to be converted to unsigned long
long, because div1 is unsigned long long, the division is done as unsigned and
in the end converted to long long for the assignment.
Comment 3 Aleksey Sanin 2006-10-12 20:28:34 EDT
With all due respect...  If you run the attached program, then you will get the
following results:

$ ./a.out
-120 / 3 = 6148914691236517165

IMHO, this makes no sense. 
Comment 4 Jakub Jelinek 2006-10-13 03:19:15 EDT
-120 / 3 is really -40, both are signed.  But that's not what you are using in
the divide.
It is -120LL / 3ULL, which is according to usual standard conversions converted
to -120ULL / 3ULL, because there is no such thing as signed/unsinged division,
C has either signed division (if both / operators are signed or converted to
signed), or unsigned division.  And, as -120ULL is 18446744073709551496 and
18446744073709551496ULL / 3ULL really is 6148914691236517165ULL.
If you want signed divide, then just cast the second operand to a signed type,
i.e.
long long res = orig / (long long) div1;
This isn't something where personal opinions should matter, the standard has
clear rules what happens when different types are used in the binary operators.
Comment 5 Aleksey Sanin 2006-10-13 03:29:22 EDT
Well, I understand these conversions... And I love standards ... but only then
it makes sense :) the above does not make any sense. Probably, compiler should
generate a warning ...

BTW, this is not a question of personal opinions. The program (compiler) does
something that user does not expect and that can cause severe bugs.

Note You need to log in before you can comment on or make changes to this bug.