Mysql - round half down or half up - mysql

Is there a way to specify if we want to round a field value 4.485 to 4.48(half down) or to 4.49 (half up) in a sql query with MySql?
Thanks

Even if the question is years old, I had the same problem and found a different solution I'd like to share.
Here it is: using a conditional function you explicitly decide which way you want to round when the last decimal is 5.
For instance, if you want to round half up with 2 decimals:
SELECT IF ((TRUNCATE(mynumber*1000,0) mod 5) = 0,CEIL(mynumber*100)/100,ROUND(mynumber,2)) as value FROM mytable
You can use FLOOR function instead of CEIL if you want to round half down; if you want a different number of decimals, say n, you change the multiplier inside the TRUNCATE call (10^n+1) and you pass it to the ROUND function.
SELECT IF ((TRUNCATE(mynumber*([10^*n+1*]),0) mod 5) = 0,[CEIL|FLOOR](mynumber*100)/100,ROUND(mynumber,[*n*])) as value FROM mytable

Based on the official MySQL documentation there is no way to specify a rounding strategy. By default, ROUND uses the “round half up” rule for exact-value numbers.
However, I needed a function that behaves like java.math.BigDecimal with java.math.RoundingMode.HALF_DOWN mode. So the only solution that I found was to create my own function.
The code (tested in MySQL 5.7)
DELIMITER //
DROP FUNCTION IF EXISTS roundHalfDown //
CREATE FUNCTION roundHalfDown (
numberToRound DECIMAL(25,15),
roundingPrecision TINYINT(2)
)
RETURNS DECIMAL(25,15)
BEGIN
DECLARE digitPosition TINYINT (2) UNSIGNED DEFAULT 0;
DECLARE digitToRound TINYINT (2) DEFAULT -1;
DECLARE roundedNumber DECIMAL(20,6) DEFAULT 0;
SET digitPosition = INSTR(numberToRound, '.');
IF (roundingPrecision < 0) THEN
SET digitPosition = digitPosition + roundingPrecision;
ELSE
SET digitPosition = digitPosition + roundingPrecision + 1;
END IF;
IF (digitPosition > 0
AND digitPosition <= CHAR_LENGTH(numberToRound)
) THEN
SET digitToRound = CAST(
SUBSTR(
numberToRound,
digitPosition,
1
) AS UNSIGNED
);
SET digitPosition = digitPosition - 1;
END IF;
IF (digitToRound > -1) THEN
IF (digitToRound > 5) THEN
SET roundedNumber = ROUND(numberToRound, roundingPrecision);
ELSEIF (digitToRound = 5 AND CAST(
SUBSTR(
REPLACE(numberToRound, '.', ''),
digitPosition + 1
) AS UNSIGNED) > 0) THEN
SET roundedNumber = ROUND(numberToRound, roundingPrecision);
ELSE
SET roundedNumber = TRUNCATE(numberToRound, roundingPrecision);
END IF;
ELSEIF (roundingPrecision > 0) THEN
SET roundedNumber = numberToRound;
END IF;
RETURN roundedNumber;
END //
DELIMITER ;
Test Cases
SELECT roundHalfDown(1.541, 2); #1.54
SELECT roundHalfDown(1.545, 2); #1.54
SELECT roundHalfDown(1.5451, 2); #1.55
SELECT roundHalfDown(1.54500001, 2); #1.55
SELECT roundHalfDown(1.54499999, 2); #1.54
SELECT roundHalfDown(-1.545, 2); #-1.54
SELECT roundHalfDown(-1.5451, 2); #-1.55
SELECT roundHalfDown(555, 0); #555
SELECT roundHalfDown(1000999, -1); #1001000
SELECT roundHalfDown(1000999, -2); #1001000
SELECT roundHalfDown(1000999, -3); #1001000
SELECT roundHalfDown(1000999, -4); #1000000
I used this answer from another Stack Overflow question, but I changed it a bit for my specific case.

Andrea Pegoretti's answer is almost correct, however it will apply when it's a 5 or a 0 instead of just a 5.
My fix (for round down):
SELECT IF (SUBSTR(TRUNCATE(CEIL(mynumber*1000),0),-1) = 5,FLOOR(mynumber*100)/100,ROUND(mynumber, 2))) AS value FROM mytable

The accepted answer of this post offers a good solution :
MySQL - How can I always round up decimals?
i.e multiply by 10^n where n is the decimal place you want to round to then call ceil to round up or floor to round down and divide by 10^n.

You can use the CEIL (or CEILING) and the FLOOR functions to round up/round down.
For example:
CEIL(4.485 * 100) / 100 will return 4.49
FLOOR(4.485 * 100) / 100 will return 4.48

I have the same question and I try all kind of custom function to emulate the functionality of PHP with a round up and down, but after a while, I figured out that MySQL produces different results using float and doubles fields.
Here my test.
mysql> describe test_redondeo;
+----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| columna_double | double(15,3) | YES | | NULL | |
| columna_float | float(9,3) | YES | | NULL | |
| id | int(11) | NO | PRI | NULL | |
+----------------+--------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
In fields type double, round function truncate, but in float round up.
mysql> select columna_double, columna_float, round ( columna_double, 2) as round_double, round ( columna_float, 2) as round_float from test_redondeo;
+----------------+---------------+--------------+-------------+
| columna_double | columna_float | round_double | round_float |
+----------------+---------------+--------------+-------------+
| 902.025 | 902.025 | 902.02 | 902.03 |
+----------------+---------------+--------------+-------------+
1 row in set (0.00 sec)
If you made a custom function for round a number using round() in any way, please pay attention to type of field.
Hope this test helps you.

According to Mysql docs,
For exact-value numbers, ROUND() uses the “round half up” rule
I had also a situation, where I was wondering why is Mysql rounding(1) 74,447 to 74,4. It was because it was in fact an endless long decimal, a approximate-value number, and not a exact-value number. The solution for me was, that I had to use ROUND() 3x like this: ROUND(ROUND(ROUND(value, 3), 2), 1). The first rounding makes sure that it's not a approximate-value number any more (where Mysql uses the “round to nearest even” rule), but a exact-value number, a decimal 3 number.

Related

How do you store and mutate a bitmap/bitset using MySQL?

I want to create a table column that stores a 500 byte bitmap (500 bytes * 8 bits per byte = 4000 bits) and do operations to mutate bits (set to 1 or 0) at certain indexes in the bitmap.
However, the documentation page on bitmaps is mostly empty leaving me with the raw bit functions as the only guide. How do you create, count, read, and mutate a bitmap as a column type in MySQL?
Using bin and lpad you can print a 64 bit number out as a binary string.
LPAD(BIN(34), 64, '0')
0000000000000000000000000000000000000000000000000000000000100010
However, how do you print out a binary/blob/varbinary string that might be 4000 bits long?
(Note: not talking about bitmap indexes)
First, upgrade to MySQL 8.0. This won't work on earlier versions of MySQL.
You should use BINARY, VARBINARY, or BLOB, depending on the length of the bitfields you want to store.
mysql> create table mytable ( bits binary(500) );
ERROR 1074 (42000): Column length too big for column 'bits' (max = 255); use BLOB or TEXT instead
mysql> create table mytable ( bits blob(500) );
Query OK, 0 rows affected (0.02 sec)
Use UNHEX() to form bitfields from hexadecimal strings. It's too difficult to work with binary bytes directly.
mysql> insert into mytable set bits = unhex(repeat('00', 500));
Query OK, 1 row affected (0.00 sec)
You can use bitwise operators like |, &, ^, and ~ with bitfields. But the strings must be the same length!
mysql> update mytable set bits = bits | b'01000';
ERROR 3513 (HY000): Binary operands of bitwise operators must be of equal length
It's inconvenient, but you must form strings that are the right length using CONCAT():
mysql> update mytable set bits = bits | unhex(concat(repeat('00', 498), 'ffff'));
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Reading a bit string (I have shortened it for this display):
mysql> select hex(bits) from mytable\G
*************************** 1. row ***************************
hex(bits): 000...000FFFF
The BIT_COUNT() function works:
mysql> select bit_count(bits) from mytable;
+-----------------+
| bit_count(bits) |
+-----------------+
| 16 |
+-----------------+
mysql> select bit_count(~bits) from mytable;
+------------------+
| bit_count(~bits) |
+------------------+
| 3984 |
+------------------+
By this time, you should sit back and think about whether SQL is the best tool for this task, even with the features of MySQL 8.0. You will find it more flexible and powerful to do bitwise work in most other programming languages.
Use BLOB as data type and write a function wich will:
extract the byte which needs to be updated
change the bit in the byte
insert the changed byte into the blob at the original position
Here is one implementation:
delimiter //
create function set_bit(b blob, pos int, val int) returns blob reads sql data
comment 'changes the bit at position <pos> (0: right most bit) to <val> in the blob <b>'
begin
declare len int; -- byte length of the blob
declare byte_pos int; -- position of the affected byte (1: left most byte)
declare bit_pos int; -- position within the affected byte (0: right most bit)
declare byte_val int; -- value of the affected byte
set len = length(b);
set byte_pos = len - (pos div 8);
set bit_pos = pos mod 8;
set byte_val = ord(substring(b, byte_pos, 1)); -- read the byte
set byte_val = byte_val & (~(1 << bit_pos)); -- set the bit to 0
set byte_val = byte_val | (val << bit_pos); -- set the bit to <val>
return insert(b, byte_pos, 1, char(byte_val)); -- replace the byte and return
end //
delimiter ;
A simple test:
create table test(id int, b blob);
insert into test(id, b) select 1, 0x000000;
insert into test(id, b) select 2, 0xffffff;
We have two blob bitmasks (3 bytes each) - One full of zeros and one full of ones. In both we set the bit at position 10 (11th bit from right) to 1 and the bit at position 11 (12th bit from right) to 0.
update test set b = set_bit(b, 10, 1);
update test set b = set_bit(b, 11, 0);
select id, hex(b), to_base2(b) from test;
Result:
| id | hex(b) | to_base2(b) |
| --- | ------ | -------------------------- |
| 1 | 000400 | 00000000 00000100 00000000 |
| 2 | FFF7FF | 11111111 11110111 11111111 |
View on DB Fiddle
Note: to_base2() is a custom function that returns a string with a bit representation of a BLOB and is only used for presentation purpose.
This works for MySQL 5.x as well as for 8.0.
It is possible to implement it inline in a single expression (without the need of a function) - But that is rather unreadable:
update test t
cross join (select 10 as pos, 1 as val) i -- input
set t.b = insert(
t.b,
length(t.b) - (i.pos div 8),
1,
char(ord(
substring(t.b, length(t.b) - (i.pos div 8), 1))
& ~(1 << (i.pos mod 8))
| (i.val << (i.pos mod 8)
))
);
View on DB Fiddle
In MySQL 8.0 it's a bit simpler, since we don't need to extract the byte and can use bit operations on blobs. But we need to make sure, that the oprerands are of the same length:
update test t
cross join (select 10 as pos, 1 as val) i -- input
set t.b = t.b
& (~(concat(repeat(0x00,length(t.b)-1),char(1)) << i.pos))
| (concat(repeat(0x00,length(t.b)-1),char(i.val)) << i.pos)
View on DB Fiddle
Another way:
update test t
cross join (select 10 as pos, 1 as val) i -- input
set t.b =
case when i.val = 1
then t.b | concat(repeat(0x00,length(t.b)-1),char(1)) << i.pos
else t.b & ~(concat(repeat(0x00,length(t.b)-1),char(1)) << i.pos)
end
View on DB Fiddle

MySQL Question - I want to eliminate all text within any parenthesis

Just checking to see if any of you would have a solution for this – from the below text like this I want to eliminate all text within any parenthesis.
Input –
PAY - addition,FILES (aaaaaaaaaaaaaa/bbbbbbbbbbbs i.e. ssss,ffff – i.e. cccccc),DED (ppppppp, llllll, fffff gggg),LOSS (ddddd, hhhhhh – i.e.),F TO G ( “F” is switching to “G”)
Output –
PAY - addition,FILES,DED,LOSS,F TO G
If you are running MySQL 8.0, you can do this with regexp_replace():
regexp_replace(mytext, '\\([^)]*\\)', '')
This works as long as there are no nested parentheses in the expression (which is consistent with your sample data).
Demo on DB Fiddle:
select regexp_replace(
'PAY - addition,FILES (aaaaaaaaaaaaaa/bbbbbbbbbbbs i.e. ssss,ffff – i.e. cccccc),DED (ppppppp, llllll, fffff gggg),LOSS (ddddd, hhhhhh – i.e.),F TO G ( “F” is switching to “G”)',
'\\([^)]*\\)',
''
) val
| val |
| :--------------------------------------- |
| PAY - addition,FILES ,DED ,LOSS ,F TO G |
Another on for MYSQL8.0:
SET #input:="PAY - addition,FILES (aaaaaaaaaaaaaa/bbbbbbbbbbbs i.e. ssss,ffff – i.e. cccccc),DED (ppppppp, llllll, fffff gggg),LOSS (ddddd, hhhhhh – i.e.),F TO G ( “F” is switching to “G”)";
with recursive cte as (
select
0 i,
#input as text
union all
select
i+1,
CASE WHEN instr(text,'(') >0 AND instr(text,')')>instr(text,'(') THEN REPLACE(text, substring(text,instr(text,'('),instr(text,')')-instr(text,'(')+1), '') ELSE '' END
from cte
where i<10
) select text from cte where text<>'' order by i desc limit 1;
output:
+------------------------------------------+
| text |
+------------------------------------------+
| PAY - addition,FILES ,DED ,LOSS ,F TO G |
+------------------------------------------+
1 row in set (0.00 sec)

How to show positions of '1' within a long number mysql

I know I can find the first position of 1 with my number by using the following:
SELECT POSITION("1" IN "0000100001000001");
How would I find all the positions of 1 to return 5;10;16
Your string appears to be a 16-bit number represented in base-2.
I set a user variable to your example string.
set #bin = '0000100001000001';
We can use CONV() to convert it to a base-10 number instead of base-2. This allows the integer value to be used when we use it in numeric expressions.
mysql> select conv(#bin, 2, 10);
+-------------------+
| conv(#bin, 2, 10) |
+-------------------+
| 2113 |
+-------------------+
Then we can test for a particular bit set in this number using the & bitwise-and operator.
mysql> select conv(#bin, 2, 10) & 64;
+------------------------+
| conv(#bin, 2, 10) & 64 |
+------------------------+
| 64 |
+------------------------+
We can test all the bits of the integer value. If a given bit is set, then substitute the "position," as you call it, for that bit (counting from left to right, which is the opposite of the traditional bit positions).
If the bit is not set, then default to NULL. Then concatenate these together using CONCAT_WS(), which ignores NULLs.
select concat_ws(';',
case conv(#bin,2,10)&32768 when 32768 then 1 end,
case conv(#bin,2,10)&16384 when 16384 then 2 end,
case conv(#bin,2,10)&8192 when 8192 then 3 end,
case conv(#bin,2,10)&4096 when 4096 then 4 end,
case conv(#bin,2,10)&2048 when 2048 then 5 end,
case conv(#bin,2,10)&1024 when 1024 then 6 end,
case conv(#bin,2,10)&512 when 512 then 7 end,
case conv(#bin,2,10)&256 when 256 then 8 end,
case conv(#bin,2,10)&128 when 128 then 9 end,
case conv(#bin,2,10)&64 when 64 then 10 end,
case conv(#bin,2,10)&32 when 32 then 11 end,
case conv(#bin,2,10)&16 when 16 then 12 end,
case conv(#bin,2,10)&8 when 8 then 13 end,
case conv(#bin,2,10)&4 when 4 then 14 end,
case conv(#bin,2,10)&2 when 2 then 15 end,
case conv(#bin,2,10)&1 when 1 then 16 end) as bits_set;
Output:
+----------+
| bits_set |
+----------+
| 5;10;16 |
+----------+
There is no such functionality built in. You can create your own function for this.
delimiter $$
create function f_position_multiple(
in_f char(1),
in_str text
)
returns text
begin
declare v_delim char(1);
declare v_loc int;
declare v_ret text;
set v_ret = '';
set v_delim = '';
set v_loc = 0;
set v_loc = locate(in_f, in_str, v_loc+1);
while(v_loc>0) do
set v_ret = concat(v_ret, v_delim, v_loc);
set v_delim = ';';
set v_loc = locate(in_f, in_str, v_loc+1);
end while;
return v_ret;
end
$$
And then you can use:
select f_position_multiple('1', '1001001')
A solution for MySql 8.0+ with a recursive CTE:
set #n = '0000100001000001';
with recursive cte as (
select 0 pos, ' ' bit
union all
select pos + 1, substring(#n, pos + 1, 1)
from cte
where pos < length(#n)
)
select group_concat(pos order by pos separator ';') result
from cte
where bit = '1'
See the demo.
Result:
| result |
| ------- |
| 5;10;16 |

Replace 44 to 0 in MYSQL?

How to replace first 44 digits to 0 in MySQL?
For example:
4476384424131 to 076384424131
Using character types and substring functions, you can simply cut off the initial '44' and replace it with a zero concatenated back on.
UPDATE tbl
SET num = CONCAT('0', SUBSTR(num, 3))
WHERE LEFT(num, 2) = '44'
(Edit: fixed the WHERE clause to use LEFT() instead of SUBSTR())
mysql> SELECT CONCAT('0', SUBSTR(4476384424131, 3));
+-----------------------------------------+
| CONCAT('0', SUBSTR(4476384424131, 3)) |
+-----------------------------------------+
| 076384424131 |
+-----------------------------------------+

MySQL Remove Trailing Zero

Is there a built-in function in MySQL the removes trailing zeros on the right?
I have samples and i want my output to be like this:
1.0 ==> 1
1.50 ==> 1.5
10.030 ==> 10.03
0.50 ==> 0.5
0.0 ==> 0
Easiest way by far, just add zero!
Examples:
SET
#yournumber1="1.0",
#yournumber2="1.50",
#yournumber3="10.030",
#yournumber4="0.50",
#yournumber5="0.0"
;
SELECT
(#yournumber1+0),
(#yournumber2+0),
(#yournumber3+0),
(#yournumber4+0),
(#yournumber5+0)
;
+------------------+------------------+------------------+------------------+------------------+
| (#yournumber1+0) | (#yournumber2+0) | (#yournumber3+0) | (#yournumber4+0) | (#yournumber5+0) |
+------------------+------------------+------------------+------------------+------------------+
| 1 | 1.5 | 10.03 | 0.5 | 0 |
+------------------+------------------+------------------+------------------+------------------+
1 row in set (0.00 sec)
If the column your value comes from is DECIMAL or NUMERIC type, then cast it to string first to make sure the conversion takes place...ex:
SELECT (CAST(`column_name` AS CHAR)+0) FROM `table_name`;
For a shorter way, just use any built-in string function to do the cast:
SELECT TRIM(`column_name`)+0 FROM `table_name`;
it solves my problem using this:
(TRIM(TRAILING '.' FROM(CAST(TRIM(TRAILING '0' FROM setpoint)AS char)))) AS setpoint
example:
mysql> SELECT testid, designationid, test, measure,
(TRIM(TRAILING '.' FROM(CAST(TRIM(TRAILING '0' FROM setpoint)AS char)))) AS setpoint,
(TRIM(TRAILING '.' FROM(CAST(TRIM(TRAILING '0' FROM tmin)AS char)))) AS tmin,
(TRIM(TRAILING '.' FROM(CAST(TRIM(TRAILING '0' FROM tmax)AS char)))) AS tmax,
FROM tests
This is my method:
SELECT TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM `table`.`column`)) FROM table
I had a similar problem in a situation where I could not modify the code nor the SQL query, but I was allowed to modify the database structure. So I changed the column format from DECIMAL to FLOAT and it solved my problem.