Bug #1723 Assigning negative value to bigint unsigned records (maxval_bigint - value)
Submitted: 31 Oct 2003 4:14 Modified: 12 Nov 2003 1:06
Reporter: [ name withheld ] Email Updates:
Status: Won't fix Impact on me:
None 
Category:MySQL Server Severity:S3 (Non-critical)
Version:4.0.13-max-log OS:Linux (Linux 2.4.18)
Assigned to: CPU Architecture:Any

[31 Oct 2003 4:14] [ name withheld ]
Description:
When I try to assign a negative value to a BIGINT UNSIGNED field, the value recorded in the database is (maxvalue_bigint - value). This behaviour doesn't occur when I use other INT-fieldtypes. Inserting a negative value of -9223372036854775809 or lower records a 0, like the rest of the INT-types do with all negative values I tried to set in an unsigned field.

I know I shouldn't insert a negative value into a unsigned field, but while hunting bugs in my own script I found out this behaviour.

How to repeat:
CREATE TABLE bigint_test (temp BIGINT UNSIGNED NOT NULL);
INSERT INTO bigint_test VALUES(-1);
INSERT INTO bigint_test VALUES('-1');
INSERT INTO bigint_test VALUES(-9223372036854775809);
INSERT INTO bigint_test VALUES(-9223372036854775808);

Output:
mysql> select * from bigint_test;
+----------------------+
| temp                 |
+----------------------+
| 18446744073709551615 |
|                    0 |
|                    0 |
|  9223372036854775808 |
+----------------------+
4 rows in set (0.00 sec)

Suggested fix:
Trying to insert any negative value records a zero in the database.
[31 Oct 2003 5:06] [ name withheld ]
This behaviour also works the other way. Trying to find out the minimum of two values with the LEAST()-function results in the previous inserted negative value. An inserted positive value shows an equal result:
INSERT INTO bigint_test VALUES(18446744073709550000);
SELECT LEAST(1, temp) AS least, temp FROM bigint_test;
[12 Nov 2003 1:06] Sergei Golubchik
I don't know what we can do here.

It's a price for having max possible integer range.
If usigned bigint would be in the range [0..9223372036854775807],
then we could be able to detect a negative value there and convert it to zero.
[4 Jun 2004 21:13] Kevin Dooley
The underlying problem seems to be that even though a BIGINT is defined as UNSIGNED, the bit-wise or operator (|) is not treating it as such. Below is an example (using binary numbers to make what's happening a little clearer) that shows what happens when you set the sign (left-most) bit on an INT (32 bits) and a BIGINT (64 bits).

I get the same result running MySQL 3.23.56 on RedHat 9, and 4.0.18-nt on Win2K.

CREATE TABLE t
(
        t32 INT UNSIGNED NOT NULL DEFAULT 0,
        t64 BIGINT UNSIGNED NOT NULL DEFAULT 0
);

INSERT INTO t SET t32=0, t64=0;
SELECT LPAD(CONV(t32, 10, 2), 32, "0") AS t32, LPAD(CONV(t64, 10, 2), 64, "0") as t64 FROM t;
+----------------------------------+------------------------------------------------------------------+
| t32                              | t64                                                              |
+----------------------------------+------------------------------------------------------------------+
| 00000000000000000000000000000000 | 0000000000000000000000000000000000000000000000000000000000000000 |
+----------------------------------+------------------------------------------------------------------+

UPDATE t SET t32 = t32 | CONV('00100000000000000000000000000000', 2, 10),
        t64 = t64 | CONV('0010000000000000000000000000000000000000000000000000000000000000', 2, 10);

SELECT LPAD(CONV(t32, 10, 2), 32, "0") AS t32, LPAD(CONV(t64, 10, 2), 64, "0") as t64 FROM t;
+----------------------------------+------------------------------------------------------------------+
| t32                              | t64                                                              |
+----------------------------------+------------------------------------------------------------------+
| 00100000000000000000000000000000 | 0010000000000000000000000000000000000000000000000000000000000000 |
+----------------------------------+------------------------------------------------------------------+

UPDATE t SET t32 = t32 | CONV('01000000000000000000000000000000', 2, 10),
        t64 = t64 | CONV('0100000000000000000000000000000000000000000000000000000000000000', 2, 10);
        
SELECT LPAD(CONV(t32, 10, 2), 32, "0") AS t32, LPAD(CONV(t64, 10, 2), 64, "0") as t64 FROM t
+----------------------------------+------------------------------------------------------------------+
| t32                              | t64                                                              |
+----------------------------------+------------------------------------------------------------------+
| 01100000000000000000000000000000 | 0110000000000000000000000000000000000000000000000000000000000000 |
+----------------------------------+------------------------------------------------------------------+

# Watch out! Here it comes!
UPDATE t SET t32 = t32 | CONV('10000000000000000000000000000000', 2, 10),
        t64 = t64 | CONV('1000000000000000000000000000000000000000000000000000000000000000', 2, 10);
SELECT LPAD(CONV(t32, 10, 2), 32, "0") AS t32, LPAD(CONV(t64, 10, 2), 64, "0") as t64 FROM t;

+----------------------------------+------------------------------------------------------------------+
| t32                              | t64                                                              |
+----------------------------------+------------------------------------------------------------------+
| 11100000000000000000000000000000 | 0111111111111111111111111111111111111111111111111111111111111111 |
+----------------------------------+------------------------------------------------------------------+

# This also happens when working with user-defined variables

SET @t32 = 0, @t64= 0;
SET @t32 = @t32 | CONV('10000000000000000000000000000000', 2, 10),
        @t64 = @t64 | CONV('1000000000000000000000000000000000000000000000000000000000000000', 2, 10);
SELECT LPAD(CONV(@t32, 10, 2), 32, "0") AS '@t32', LPAD(CONV(@t64, 10, 2), 64, "0") as '@t64';
+----------------------------------+------------------------------------------------------------------+
| @t32                             | @t64                                                             |
+----------------------------------+------------------------------------------------------------------+
| 10000000000000000000000000000000 | 0111111111111111111111111111111111111111111111111111111111111111 |
+----------------------------------+------------------------------------------------------------------+

# Interestingly, you can initialize a BIGINT with just the left most bit set, you just can't 'or' it and preserve the other bits...

DELETE FROM t;
INSERT INTO t SET t32 = CONV('10000000000000000000000000000000', 2, 10),
        t64 = CONV('1000000000000000000000000000000000000000000000000000000000000000', 2, 10)
SELECT LPAD(CONV(t32, 10, 2), 32, "0") AS '@t32', LPAD(CONV(t64, 10, 2), 64, "0") as '@t64' FROM t
+----------------------------------+------------------------------------------------------------------+
| @t32                             | @t64                                                             |
+----------------------------------+------------------------------------------------------------------+
| 10000000000000000000000000000000 | 1000000000000000000000000000000000000000000000000000000000000000 |
+----------------------------------+------------------------------------------------------------------+

Kevin
B=)x