diff --git a/mysql-test/r/func_math.result b/mysql-test/r/func_math.result index 4a1722f4045..a2cab6cf04d 100644 --- a/mysql-test/r/func_math.result +++ b/mysql-test/r/func_math.result @@ -659,6 +659,27 @@ ERROR 22003: BIGINT value is out of range in '-(`test`.`t1`.`a`)' SELECT -b FROM t1; ERROR 22003: BIGINT value is out of range in '-(`test`.`t1`.`b`)' DROP TABLE t1; +# +# Bug #96470 Negating operator refines to DECIMAL_RESULT for constant negative integers +# Notice the display width of integer is not part of data type, +# should any fix to display width changes the outcomes, feel free to fix these tests as well. +# +CREATE TABLE t1 SELECT +- - 5, +- - 9223372036854775808; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `- - 5` int(3) NOT NULL DEFAULT '0', + `- - 9223372036854775808` decimal(20,0) NOT NULL DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +DROP TABLE t1; +CREATE TABLE t1 (col2 int(11) DEFAULT NULL); +INSERT INTO t1 VALUES (1); +SELECT - - 5 + 96 DIV + col2 FROM t1; +- - 5 + 96 DIV + col2 +101 +DROP TABLE t1; SET @a:=999999999999999999999999999999999999999999999999999999999999999999999999999999999; SELECT @a + @a; ERROR 22003: DECIMAL value is out of range in '((@`a`) + (@`a`))' diff --git a/mysql-test/t/func_math.test b/mysql-test/t/func_math.test index 5d03c5f9066..262fb09e565 100644 --- a/mysql-test/t/func_math.test +++ b/mysql-test/t/func_math.test @@ -488,6 +488,23 @@ SELECT -b FROM t1; DROP TABLE t1; +-- echo # +-- echo # Bug #96470 Negating operator refines to DECIMAL_RESULT for constant negative integers +-- echo # Notice the display width of integer is not part of data type, +-- echo # should any fix to display width changes the outcomes, feel free to fix these tests as well. +-- echo # +CREATE TABLE t1 SELECT + - - 5, + - - 9223372036854775808; +SHOW CREATE TABLE t1; +DROP TABLE t1; + +# Aone #21114225 [rqg]DIV + const expression return wrong data +CREATE TABLE t1 (col2 int(11) DEFAULT NULL); +INSERT INTO t1 VALUES (1); +SELECT - - 5 + 96 DIV + col2 FROM t1; +DROP TABLE t1; + # Decimal overflows # ================= diff --git a/sql/item_func.cc b/sql/item_func.cc index 06785220a39..72c1a2fb3f1 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2076,9 +2076,43 @@ bool Item_func_neg::resolve_type(THD *thd) { */ if (hybrid_type == INT_RESULT && args[0]->const_item()) { longlong val = args[0]->val_int(); - if ((ulonglong)val >= (ulonglong)LLONG_MIN && - ((ulonglong)val != (ulonglong)LLONG_MIN || - args[0]->type() != INT_ITEM)) { + /* + Bug#96470 + + Since arg0 can be either signed or unsigned, val should be treated + as a 64-bit value instead of a signed value, and the interpretation + of bit pattern depends on the unsigned_flag. + + Here uses 8-bit as an example; the same rule applies to 64-bit. + + bit pattern 00000000 ... 01111111 10000000 ... 11111111 + unsigned 0 127 128 255 + signed 0 127 -128(INT8_MIN) -1 + + See https://en.wikipedia.org/wiki/Signed_number_representations + + Intuitively, for unsigned numbers, negating any value to the right + of 128 gets out-of-range; for signed numbers, only negating -128 + itself gets out-of-range. + + Since both 128 and -128 use the same bit pattern (INT8_MIN), for the + right half of the bit patterns (1), we have the following truth table: + + arg0 is bitpat is outcome of + unsigned INT8_MIN negating operator + true true ok + true false out-of-range + false true out-of-range + false false ok + + So exclusive-or the left two columns gets out-of-range. (2) + + With respect to out-of-range test, only the type and the value of + args[0] are significant, while the node representation is ignored. + */ + if ((ulonglong)val >= (ulonglong)LLONG_MIN && // (1) + args[0]->unsigned_flag != // (2) + ((ulonglong)val == (ulonglong)LLONG_MIN)) { /* Ensure that result is converted to DECIMAL, as longlong can't hold the negated number