Different values for these two rounding expressions? - sql

SELECT
ROUND(152.7300, 2, 1), --returns 152.7300
ROUND('152.7300', 2, 1) --returns 152.72
Why does the second rounding expression result in downward rounding to .72 ?
I imagine it has something to do with the VARCHAR input being converted to a numeric, but I can't imagine how that would cause it to lose a decimal value like that.

When you pass a string literal to ROUND, SQL Server converts it to float*. In contrast, 152.7300 is treated as numeric:
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.
Exact float representation of 152.73 is 152.72999572753906, hence truncation** to the second decimal digit yields 152.72.
* You can check this by passing a non-numeric string, e.g. ROUND('foo', 2, 1). This will produce "Error converting data type varchar to float."
** Passing a value other than zero for the third parameter of ROUND indicates truncation of the number, instead of rounding.

https://learn.microsoft.com/en-us/sql/t-sql/functions/round-transact-sql
ROUND ( numeric_expression , length [ ,function ] )
function
Is the type of operation to perform. function must be tinyint, smallint, or int. When function is omitted or has a value of 0 (default), numeric_expression is rounded. When a value other than 0 is specified, numeric_expression is truncated.

Related

How Can I Get An Exact Character Representation of a Float in SQL Server?

We are doing some validation of data which has been migrated from one SQL Server to another SQL Server. One of the things that we are validating is that some numeric data has been transferred properly. The numeric data is stored as a float datatype in the new system.
We are aware that there are a number of issues with float datatypes, that exact numeric accuracy is not guaranteed, and that one cannot use exact equality comparisons with float data. We don't have control over the database schemas nor data typing and those are separate issues.
What we are trying to do in this specific case is verify that some ratio values were transferred properly. One of the specific data validation rules is that all ratios should be transferred with no more than 4 digits to the right of the decimal point.
So, for example, valid ratios would look like:
.7542
1.5423
Invalid ratios would be:
.12399794301
12.1209377
What we would like to do is count the number of digits to the right of the decimal point and find all cases where the float values have more than four digits to the right of it. We've been using the SUBSTRING, LEN, STR, and a couple of other functions to achieve this, and I am sure it would work if we had numeric fields typed as decimal which we were casting to char.
However, what we have found when attempting to convert a float to a char value is that SQL Server seems to always convert to decimal in between. For example, the field in question shows this value when queried in SQL Server Enterprise Manager:
1.4667
Attempting to convert to a string using the recommended function for SQL Server:
LTRIM(RTRIM(STR(field_name, 22, 17)))
Returns this value:
1.4666999999999999
The value which I would expect if SQL Server were directly converting from float to char (which we could then trim trailing zeroes from):
1.4667000000000000
Is there any way in SQL Server to convert directly from a float to a char without going through what appears to be an intermediate conversion to decimal along the way? We also tried the CAST and CONVERT functions and received similar results to the STR function.
SQL Server Version involved: SQL Server 2012 SP2
Thank you.
Your validation rule seems to be misguided.
An SQL Server FLOAT, or FLOAT(53), is stored internally as a 64-bit floating-point number according to the IEEE 754 standard, with 53 bits of mantissa ("value") plus an exponent. Those 53 binary digits correspond to approximately 15 decimal digits.
Floating-point numbers have limited precision, which does not mean that they are "fuzzy" or inexact in themselves, but that not all numbers can be exactly represented, and instead have to be represented using another number.
For example, there is no exact representation for your 1.4667, and it will instead be stored as a binary floating-point number that (exactly) corresponds to the decimal number 1.466699999999999892708046900224871933460235595703125. Correctly rounded to 16 decimal places, that is 1.4666999999999999, which is precisely what you got.
Since the "exact character representation of the float value that is in SQL Server" is 1.466699999999999892708046900224871933460235595703125, the validation rule of "no more than 4 digits to the right of the decimal point" is clearly flawed, at least if you apply it to the "exact character representation".
What you might be able to do, however, is to round the stored number to fewer decimal places, so that the small error at the end of the decimals is hidden. Converting to a character representation rounded to 15 instead of 16 places (remember those "15 decimal digits" mentioned at the beginning?) will give you 1.466700000000000, and then you can check that all decimals after the first four are zeroes.
You can try using cast to varchar.
select case when
len(
substring(cast(col as varchar(100))
,charindex('.',cast(col as varchar(100)))+1
,len(cast(col as varchar(100)))
)
) = 4
then 'true' else 'false' end
from tablename
where charindex('.',cast(col as varchar(100))) > 0
For this particular number, don't use STR(), and use a convert or cast to varchar. But, in general, you will always have precision issues when storing in float... it's the nature of the storage of that datatype. The best you can do is normalize to a NUMERIC type and compare with threshold ranges (+/- .0001, for example). See the following for a breakdown of how the different conversions work:
declare #float float = 1.4667
select #float,
convert(numeric(18,4), #float),
convert(nvarchar(20), #float),
convert(nvarchar(20), convert(numeric(18,4), #float)),
str(#float, 22, 17),
str(convert(numeric(18,4), #float)),
convert(nvarchar(20), convert(numeric(18,4), #float))
Instead of casting to a VarChar you might try this: cast to a decimal with 4 fractional digits and check if it's the same value as before.
case when field_name <> convert(numeric(38,4), field_name)
then 1
else 0
end
The issue you have here is that float is an approximate number data type with an accuracy of about seven digits. That means it approaches the value while using less storage than a decimal / numeric. That's why you don't use float for values that require exact precision.
Check this example:
DECLARE #t TABLE (
col FLOAT
)
INSERT into #t (col)
VALUES (1.4666999999999999)
,(1.4667)
,(1.12399794301)
,(12.1209377);
SELECT col
, CONVERT(NVARCHAR(MAX),col) AS chr
, CAST(col as VARBINARY) AS bin
, LTRIM(RTRIM(STR(col, 22, 17))) AS rec
FROM #t
As you see the float 1.4666999999999999 binary equals 1.4667. For your stated needs I think this query would fit:
SELECT col
, RIGHT(CONVERT(NVARCHAR(MAX),col), LEN(CONVERT(NVARCHAR(MAX),col)) - CHARINDEX('.',CONVERT(NVARCHAR(MAX),col))) AS prec
from #t

STR function in SQL

SELECT ltrim (STR (1234.34968311,44,16));
Result: 1234.3496831099999000
SELECT ltrim (STR (123.34968311,44,16));
Result: 123.3496831100000000
Can someone please help me to understand why the first query returns the varchar value which is not exactly the same as the input.
Because STR operates on a FLOAT datatype so your decimal number constant is converted to a FLOAT before being transformed into a VARCHAR of the requested size. And as we all know, FLOAT values are an approximation of the number you thought you would get :-) so you end up with differing values after the decimal point.
For example, try STR (1234.1,44,16) and you get 1234.0999999999999000.
If you read the manual at http://msdn.microsoft.com/en-us/library/ms189527.aspx it says float_expression - Is an expression of approximate numeric (float) data type with a decimal point. Yes, APPROXIMATE.

SQL Character extraction

I would like to extract everything on the right side of the "underscore". I want to get 0000. I tried
select right('M_0000',charindex('_','M_0000')-1)
but end up with just 0. Why?
Since you are using CHARINDEX, i assume you are using SQL-SERVER.
So in your second field you should say how many characters you want. You can achieve that by doing LEN('M_0000') - charindex('_') :
select right('M_0000',len('M_0000') - charindex('_','M_0000'))
sqlfiddle demo
You were ending up with just one 0 because charindex('_') is 1, and you are telling the RIGHT function that you want 1 char from the right.
From the docs:
RIGHT ( character_expression , integer_expression )
character_expression Is an expression of character or binary data.
character_expression can be a constant, variable, or column.
character_expression can be of any data type, except text or ntext,
that can be implicitly converted to varchar or nvarchar. Otherwise,
use the CAST function to explicitly convert character_expression.
integer_expression Is a positive integer that specifies how many
characters of character_expression will be returned. If
integer_expression is negative, an error is returned. If
integer_expression is type bigint and contains a large value,
character_expression must be of a large data type such as
varchar(max).
You can use SUBSTRING instead of RIGHT:
select SUBSTRING('M_0000',charindex('_','M_0000')+1, LEN('M_0000')) // start at character n + 1

dealing with numbers after float point

I am just stuck in this situation. Is there any way in SQL
to get rid off from all numbers except two numbers after decimal point?
for example, lets say 125.36987 so as the output i want only 125.36.
Ive tried to multiply it to 100 then use cast it to int and divide by 100.0
but still getting not what i want.
YES,ROUND CAN SOLVE THAT.
SELECT ROUND(125.36987,2) FROM DUAL; YOU CAN GET 125.37
Or if YOU DON'T NEED round-off , you can use 'trunc'
like this TRUNC(125.36987,2) 125.36
use ROUND()
select round(125.36987, 2, 1)
SQLFiddle Demo
ROUND
Here's the detail. the syntax,
ROUND ( numeric_expression , length [ ,function ] )
function
Is the type of operation to perform. function must be tinyint,
smallint, or int. When function is omitted or has a value of 0
(default), numeric_expression is rounded. When a value other than 0 is
specified, numeric_expression is truncated.

How to reduce the float length

Using SQL Server 2000
I want to reduce the decimal length
Query
Select 23/12 as total
Output is showing as 1.99999999999
I don't want to round the value, I want to diplay like this 1.99
Tried Query
Select LEFT(23/12, LEN(23/12) - 3) as total
The above query is working only if there is decimal value like 12.444444, but if the total is single digit means like 12 or 4 or 11...., i am getting error at run time.
How to do this.
Need Query Help
There is a very simple solution. You can find it in BOL. Round takes an optional 3rd argument, which is round type. The values are round or truncate.
ROUND numeric_expression , length [ ,function ] )
...
function Is the type of operation to perform. function must be
tinyint, smallint, or int. When function is omitted or has a value of
0 (default), numeric_expression is rounded. When a value other than 0
is specified, numeric_expression is truncated.
So just do
Select ROUND(cast(23 as float)/12, 2, 1) as total
That gives 1.91. Note, if you were really seeing 1.999 - something is really wrong with your computer. 23/12 = 1.916666666(ad infinitum). You need to cast one of the numbers as float since sql is assuming they're integers and doing integer division otherwise. You can of course cast them both as float, but as long as one is float the other will be converted too.
Not terribly elegant, but works for all cases:
Select CONVERT(float,LEFT(CONVERT(nvarchar, 23.0/12.0),CHARINDEX('.',CONVERT(nvarchar, 23.0/12.0)) + 2)) as total
Scalar Function
-- Description: Truncate instead of rounding a float
-- SELECT dbo.TruncateNumber(23.0/12.0,2)
-- =============================================
CREATE FUNCTION TruncateNumber
(
-- Add the parameters for the function here
#inFloat float,
#numDecimals smallint
)
RETURNS float
AS
BEGIN
IF (#numDecimals < 0)
BEGIN
SET #numDecimals = 0
END
-- Declare the return variable here
RETURN CONVERT(float,LEFT(CONVERT(nvarchar, #inFloat),CHARINDEX('.',CONVERT(nvarchar, #inFloat)) + #numDecimals))
END
GO