Bug 210529

Summary: 64-bit signed / unsigned division gives the wrong result on RHEL4 64-bit with gcc 3.4.6
Product: Red Hat Enterprise Linux 4 Reporter: Chris Palmer <chris>
Component: gcc3Assignee: Jakub Jelinek <jakub>
Status: CLOSED NOTABUG QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: 4.0CC: aleksey
Target Milestone: ---   
Target Release: ---   
Hardware: x86_64   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2006-10-12 21:00:48 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Attachments:
Description Flags
test program for divide bug none

Description Chris Palmer 2006-10-12 18:56:57 UTC
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 18:56:57 UTC
Created attachment 138353 [details]
test program for divide bug

Comment 2 Jakub Jelinek 2006-10-12 21:00:48 UTC
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-13 00:28:34 UTC
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 07:19:15 UTC
-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 07:29:22 UTC
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.