Round to n Significant Figures in SQL - sql

I would like to be able to round a number to n significant figures in SQL. So:
123.456 rounded to 2sf would give 120
0.00123 rounded to 2sf would give 0.0012
I am aware of the ROUND() function, which rounds to n decimal places rather than significant figures.

select round(#number,#sf-1- floor(log10(abs(#number)))) should do the trick !
Successfully tested on your two examples.
Edit : Calling this function on #number=0 won't work. You should add a test for this before using this code.
create function sfround(#number float, #sf int) returns float as
begin
declare #r float
select #r = case when #number = 0 then 0 else round(#number ,#sf -1-floor(log10(abs(#number )))) end
return (#r)
end

Adapted the most popular answer by Brann to MySQL for those who come looking like me.
CREATE FUNCTION `sfround`(num FLOAT, sf INT) # creates the function
RETURNS float # defines output type
DETERMINISTIC # given input, will return same output
BEGIN
DECLARE r FLOAT; # make a variable called r, defined as a float
IF( num IS NULL OR num = 0) THEN # ensure the number exists, and isn't 0
SET r = num; # if it is; leave alone
ELSE
SET r = ROUND(num, sf - 1 - FLOOR(LOG10(ABS(num))));
/* see below*/
END IF;
RETURN (r);
END
/* Felt too long to put in comment */
ROUND(num, sf - 1 - FLOOR(LOG10(ABS(num))))
The part that does the work - uses ROUND function on the number as normal, but the length to be rounded to is calculated
ABS ensures positive
LOG10 gets the number of digits greater than 0 in the number
FLOOR gets the largest integer smaller than the resultant number
So always rounds down and gives an integer
sf - 1 - FLOOR(...) gives a negative number
works because ROUND(num, -ve num) rounds to the left of the decimal point
For just a one off, ROUND(123.456, -1) and ROUND(0.00123,4)
return the requested answers ((120, 0.0012)

I think I've managed it.
CREATE FUNCTION RoundSigFig(#Number float, #Figures int)
RETURNS float
AS
BEGIN
DECLARE #Answer float;
SET #Answer = (
SELECT
CASE WHEN intPower IS NULL THEN 0
ELSE FLOOR(fltNumber * POWER(CAST(10 AS float), intPower) + 0.5)
* POWER(CAST(10 AS float), -intPower)
END AS ans
FROM (
SELECT
#Number AS fltNumber,
CASE WHEN #Number > 0
THEN -((CEILING(LOG10(#Number)) - #Figures))
WHEN #Number < 0
THEN -((FLOOR(LOG10(#Number)) - #Figures))
ELSE NULL END AS intPower
) t
);
RETURN #Answer;
END

You could divide by 100 before rounding and then multiplying by 100...

Related

SQL 2017 rounds of decimal number using "FLOOR" function

I have been calculating different integer percentages with different numbers but each time I get floor rounded number. select 13*100/60 gives me 21 and the actual number is 21.66 which using a round function should give us 22 but it can only give me 21 for all different decimal numbers.
I am using SQL 2017. please help
This is due to the fact that you are dividing ints and not floating-point numbers. Integer division returns an integer.
Try the following instead (noting the .0 on the end of the 60):
SELECT 13 * 100 / 60.0
Making one of the components a floating-point number will automatically output the result as a floating-point number.
Output:
21.666666
Incidentally, if you are working with variables and one of them is a FLOAT, it will automatically produce the output you expect:
DECLARE #A FLOAT
DECLARE #B INT
DECLARE #C INT
SET #A = 13
SET #B = 100
SET #C = 60
SELECT #A * #B / #C
Output:
21.6666666666667

Find next greater number with zeros in sql number

I'm looking for a way to find next greater number starting by 1 and followed by zeros in Microsoft SQL. Numbers could vary in digits. ie:
Query: 9856, Result after procedure: 10000
Query: 98999, Result after procedure: 100000
Thanks.
EDIT: There is no chance of having negative numbers. This is a calculation for a energy meter. For example, numbers can go up to 99999 or 999999 or 9999999. When energy overcome that number, it will start again at 0. So I can't read what energy has been used in that period. To know it, I need to calculate the number as asked, then perform some basic maths.
There is no need for knowing what is going on on 10, 100, etc, because of the nature of the operation. It will only be used when the above escenario happend.
I don't know why you require or mathematically any other formula can be implemented. but technically that can be achieved as follows
DECLARE #count INT
SET #count = 1000
DECLARE #result INT
SET #result = CASE WHEN #count%10 = 0 THEN #count ELSE CAST('1'+REPLICATE('0',LEN(#count)) AS INT) end
SELECT #result
This works for positive numbers (numbers greater than zero):
select power(10, ceiling( log10(the_number) )) from mytable;
In case the number is already a power of ten (1, 10, 100, ...) , the number itself is returned.
You can do this with just arithmetic operations:
select power(10, floor(log(v.n - 0.1, 10)) + 1)
from (values (1), (10), (8), (9982), (124)) v(n)
This is a fairly crude way of doing it, however it does work. The query basically looks at the number of digits your number has, assumes the next integer you want starts with a 1 and then adds the relevant number of 0's to it.
Note this only looks for the next increment and does not round down.
Also for 10 you will get 100 and for 1000 you will get 10000.
declare #number int = 98999;
declare #len int = len(#number);
declare #stringtoreturn nvarchar(200)='1';
declare #runs int = 1;
while #runs<=#len
begin
select #stringtoreturn = #stringtoreturn + '0';
select #runs=#runs+1;
end
select #stringtoreturn;

Round off to the smallest integer value

I'm using this query to round off the numbers and this round off the next value. Now I need to round off to the before value means if the value is 45.67 then the value should be 45. I tried these two queries and still I need to tweak the values.
Method1:
parsename('$' + convert(varchar,convert(money,round(sum(Column1 * Column2),0)),1),2)
Method2:
parsename('$' + convert(varchar,convert(money,floor(Column1 * Column2),0),1),2)
Really appreciate any suggestions.
The CEILING function returns the smallest integer greater than or equal to the specified numeric expression. The FLOOR function returns the largest integer less than or equal to the specified numeric expression. For example, in considering a numeric expression of 12.9273, CEILING returns 13 and FLOOR returns 12. The return value of both FLOOR and CEILING has the same data type as the input numeric expression.
SELECT CEILING(12.9273);
Here is the result set.
13
SELECT FLOOR(12.9273);
Here is the result set.
12
http://technet.microsoft.com/en-us/library/ms190927%28v=sql.105%29.aspx
To round down you can use FLOOR()
E.G.
DECLARE #number numeric(5,2)
SET #number = 45.67
SELECT FLOOR(#number)
You'd get the result 45
With your example, it looks like it's already working?
declare #number1 numeric(5,2)
declare #number2 numeric(5,2)
set #number1 = 1.23
set #number2 = 21.69
select parsename('$' + convert(varchar,convert(money,floor(#number1 * #number2),0),1),2)
select #number1 * #number2
Results
$26
26.6787

SQL Server Rounding Issue where there is 5

As far as i know according to mathematics rounding should work as below when rounding number is 5.
2.435 => 2.44 (Round Up, if rounding to digit(3) is odd number)
2.445 => 2.44 (Round Down, if rounding to digit(4) is even number)
if we do summation all fine,
2.435 + 2.445 = 4.88
2.44 + 2.44 = 4.88
I'm pretty sure in .Net also rounding works like this.
But in SQL server, 5 is always rounding up which is not correct according to maths.
SELECT round(2.345, 2) = 2.35
SELECT round(2.335, 2) => 2.34
this results to 1 cent discrepancies in summation of rounded values.
2.345 + 2.335 = 4.68
2.35 + 2.34 = 4.69 => which is not correct
I have tried this with decimal and money data types.
Am i doing something wrong? Is there a work around for this?
If you do want to use banker's rounding in SQL Server...
CREATE FUNCTION BankersRounding(#value decimal(36,11), #significantDigits INT)
RETURNS MONEY
AS
BEGIN
-- if value = 12.345 and signficantDigits = 2...
-- base = 1000
declare #base int = power(10, #significantDigits + 1)
-- roundingValue = 12345
declare #roundingValue decimal(36,11) = floor(abs(#value) * #base)
-- roundingDigit = 5
declare #roundingDigit int = #roundingValue % 10
-- significantValue = 1234
declare #significantValue decimal(36,11) = floor(#roundingValue / 10)
-- lastSignificantDigit = 4
declare #lastSignificantDigit int = #significantValue % 10
-- awayFromZero = 12.35
declare #awayFromZero money = (#significantValue + 1) / (#base / 10)
-- towardsZero = 12.34
declare #towardsZero money = #significantValue / (#base / 10)
-- negative values handled slightly different
if #value < 0
begin
-- awayFromZero = -12.35
set #awayFromZero = ((-1 * #significantValue) - 1) / (#base / 10)
-- towardsZero = -12.34
set #towardsZero = (-1 * #significantValue) / (#base / 10)
end
-- default to towards zero (i.e. assume thousandths digit is 0-4)
declare #rv money = #towardsZero
if #roundingDigit > 5
set #rv = #awayFromZero -- 5-9 goes away from 0
else if #roundingDigit = 5
begin
-- 5 goes to nearest even number (towards zero if even, away from zero if odd)
set #rv = case when #lastSignificantDigit % 2 = 0 then #towardsZero else #awayFromZero end
end
return #rv
end
You're looking for Banker's Rounding - which is the default rounding in C# but is not how SQL Server ROUND() works.
See Why does TSQL on Sql Server 2000 round decimals inconsistently? as well as http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/sql-server-rounding-methods and http://www.chrispoulter.com/blog/post/rounding-decimals-using-net-and-t-sql
Mathematically rounding up at 5 is correct, and also the most commonly used type of rounding in basic mathematics. Other types of rounding are also valid, but are not basic mathematics, but more often used in certain areas due to 0.5 often being a dispute number.
What you call mathematically rounding is actually bankers rounding, which is the type of rounding used in the finance business.

SQL floating-point precision limited to 6 digits

I have the following set of calculations in excel that I want to be able to use in a stored procedure.
Excel
CellA: 45/448.2 = 0.100401606425703
CellB: 1-CellA = 0.899598393574297
CellC: 1-CellB = 0.100401606425703
CellD: CellC * 448.2 = 45.000000000000000
In SQL I am doing the following:
declare #a decimal(18,15) = 45/448.2
declare #b decimal(18,15) = 1-#a
declare #c decimal(18,15) = 1-#b
declare #d decimal(18,15) = #c * 448.2
I have also tried running the calculation in one line
declare #e decimal(18,15) = (1-(1-(45/448.2)))*448.2
when I return the values SQL gives me the following:
#a: 0.100401000000000
#b: 0.899599000000000
#c: 0.100401000000000
#d: 44.999728200000000
#e: 44.999728200000000
I've tried adjusting the precision of the decimals in SQL but I nothing makes a difference, it only returns the first 6 digits of the decimal.
Does Excel do any optimization when running the formula?
Any ideas?
Even just your first line is enough to show the problem:
declare #a decimal(18,15) = 45/448.2
print #a
gives
---------------------------------------
0.100401000000000
This is because of data types. When you say
448.2
it is (per the documentation) interpreted as a constant of type decimal, and also per the documentation,
In Transact-SQL statements, a constant with a decimal point is
automatically converted into a numeric data value, using the minimum
precision and scale necessary. For example, the constant 12.345 is
converted into a numeric value with a precision of 5 and a scale of 3.
So 448.2 is decimal(4,3). 45 is integer, which when combined with a decimal is treated as having precision of 10 and scale 0 . When we divide, the rules say
Operation Result precision Result scale
e1 / e2 p1 - s1 + s2 + max(6, s1 + p2 + 1) max(6, s1 + p2 + 1)
which in this case gives a result precision of 10 - 3 + 0 + max(6, 0 + 3 + 1) and scale of max(6, 0 + 3 + 1), which comes out to 13 and 6.
That result scale of 6 is why the result only has those six decimal places.
The way to fix it is to get your operands into an appropriate type before acting on them; for example, here are two ways:
Force a number to be treated as floating-point:
declare #a decimal(18,15) = 45/448.2e0
select #a
---------------------------------------
0.100401606425703
Explicitly supply a decimal scale:
declare #a decimal(18,15) = 45/cast(448.2 as decimal(18,10))
select #a
---------------------------------------
0.100401606425703