13

I have a column X which is full of floats with decimals places ranging from 0 (no decimals) to 6 (maximum). I can count on the fact that there are no floats with greater than 6 decimal places. Given that, how do I make a new column such that it tells me how many digits come after the decimal?

I have seen some threads suggesting that I use CAST to convert the float to a string, then parse the string to count the length of the string that comes after the decimal. Is this the best way to go?

Charles
  • 50,943
  • 13
  • 104
  • 142
MrPatterns
  • 4,184
  • 27
  • 65
  • 85

10 Answers10

26

You can use something like this:

declare @v sql_variant

set @v=0.1242311

select SQL_VARIANT_PROPERTY(@v, 'Scale') as Scale

This will return 7.


I tried to make the above query work with a float column but couldn't get it working as expected. It only works with a sql_variant column as you can see here: http://sqlfiddle.com/#!6/5c62c/2

So, I proceeded to find another way and building upon this answer, I got this:

SELECT value,
LEN(
    CAST(
         CAST(
              REVERSE(
                      CONVERT(VARCHAR(50), value, 128)
                     ) AS float
             ) AS bigint
        )
   ) as Decimals
FROM Numbers

Here's a SQL Fiddle to test this out: http://sqlfiddle.com/#!6/23d4f/29


To account for that little quirk, here's a modified version that will handle the case when the float value has no decimal part:

SELECT value,
       Decimals = CASE Charindex('.', value)
                    WHEN 0 THEN 0
                    ELSE
           Len (
            Cast(
             Cast(
              Reverse(CONVERT(VARCHAR(50), value, 128)) AS FLOAT
                 ) AS BIGINT
                )
               )
                    END
FROM   numbers

Here's the accompanying SQL Fiddle: http://sqlfiddle.com/#!6/10d54/11

Community
  • 1
  • 1
Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
  • 1
    Cool trick, although I think you mean `Scale`, not `Precision`. – Joachim Isaksson Feb 05 '13 at 19:31
  • 2
    Can you use this on a column of float? – Tim Lehner Feb 05 '13 at 19:45
  • 2
    @TimLehner Afaik not with a straight forward SQL query, the `set` will set the variant type to a numeric with 7 decimals (ie the minimum required to store the value), and `Scale` will return the precision of the _datatype_, not the value. When you select from a table column, you'll get the Scale of the column _type_, not the scale of the actual number. – Joachim Isaksson Feb 05 '13 at 19:56
  • @JoachimIsaksson: You're absolutely right. I tried to get that working with a `float` column but for the life of me, I couldn't. Just provided another way to get there. – Leniel Maccaferri Feb 05 '13 at 21:15
  • +1, good find on `convert(...128)`, though [MSDN](http://msdn.microsoft.com/en-us/library/ms187928.aspx) does say "Included for legacy reasons and might be deprecated in a future release." – Tim Lehner Feb 05 '13 at 21:41
  • @Leniel. Thank you for your help. One question, why do we need a second CAST (the outer cast, not the inner one to convert to bigint)? Also why do you need LEN as Decimals instead of just LEN? – MrPatterns Feb 07 '13 at 15:22
  • The second cast will ignore the decimals after the reverse, so if you have 1.2345 after the reverse operation you'll have 5432.1. Converting this to `bigint` you'll get 5432. Then you use LEN to count the length on this value = 4. `As Decimals` is just a name I gave to the column. You could have used just LEN and the column name would be generated automatically by SQL Server. – Leniel Maccaferri Feb 07 '13 at 15:29
  • Ok, I think I mistyped my last question. It should read, what does the first CAST statement do? Why do we need it? – MrPatterns Feb 07 '13 at 15:51
  • OK, that's basically casting the `bigint` to a string so that the LEN function http://msdn.microsoft.com/en-us/library/ms190329.aspx can work on it. :) – Leniel Maccaferri Feb 07 '13 at 16:05
  • Ah, so by default CAST will cast to a string if no other datatype is specified. Thank you for explaining this. – MrPatterns Feb 07 '13 at 16:11
  • 1
    I just found a little quirk. If I have ZERO decimals as mentioned in my question, then the values 1, 12, 123, and 1234 will yield 1, 2, 3, 4 as results when in fact the result should be 0. Basically, reversing and casting as bigint to chop off digits doesn't work if there are 0 decimal places as mentioned in the question. Am I right? Thank you. – MrPatterns Feb 07 '13 at 16:39
  • You're absolutely right... I'll try to get a solution to this. :) – Leniel Maccaferri Feb 07 '13 at 16:44
  • Thank you. I learned 2 new concepts today: Case and CharIndex. – MrPatterns Feb 07 '13 at 22:24
  • I'm getting "Error converting data type varchar to float." Any guesses as to why that might be? I can't find any unusual values in my column. – influent Dec 12 '13 at 19:51
  • @influent: Are you sure your column is of type `float`? – Leniel Maccaferri Dec 12 '13 at 19:59
  • ABS() may be required in general use cases, because it fails with "Error converting data type varchar to float." with negative values. – AjV Jsy Feb 04 '19 at 17:48
  • 1
    I was getting some false positives with the CHARINDEX approach. Doing a `RIGHT(LTRIM(STR(MyColumn, 50, 10)), 10) = '0000000000'` check seems to work better. – Josh Jay Sep 04 '19 at 14:55
6

This thread is also using CAST, but I found the answer interesting:

http://www.sqlservercentral.com/Forums/Topic314390-8-1.aspx

DECLARE @Places INT
 SELECT TOP 1000000 @Places = FLOOR(LOG10(REVERSE(ABS(SomeNumber)+1)))+1
   FROM dbo.BigTest

and in ORACLE:

SELECT FLOOR(LOG(10,REVERSE(CAST(ABS(.56544)+1 as varchar(50))))) + 1 from DUAL
vbaranov
  • 195
  • 8
  • Interesting idea, but sadly breaks down due to default rounding in TSQL (float to varchar cuts at 6 digits) A manual cast may fix that, but complicates the query further. – Joachim Isaksson Feb 05 '13 at 19:47
  • Works well for finding decimals that have high precision in my data set – fiat May 19 '21 at 03:47
3

A float is just representing a real number. There is no meaning to the number of decimal places of a real number. In particular the real number 3 can have six decimal places, 3.000000, it's just that all the decimal places are zero.

You may have a display conversion which is not showing the right most zero values in the decimal.

Note also that the reason there is a maximum of 6 decimal places is that the seventh is imprecise, so the display conversion will not commit to a seventh decimal place value.

Also note that floats are stored in binary, and they actually have binary places to the right of a binary point. The decimal display is an approximation of the binary rational in the float storage which is in turn an approximation of a real number.

So the point is, there really is no sense of how many decimal places a float value has. If you do the conversion to a string (say using the CAST) you could count the decimal places. That really would be the best approach for what you are trying to do.

Marlin Pierce
  • 9,931
  • 4
  • 30
  • 52
  • 1
    You cannot truly find the number of decimal places if you do conversion to string. Conversion will round off or truncate the decimal places. – Amit Rai Sharma May 20 '13 at 11:20
  • I came up with a better way to explain. In the ruby console, typing "1.1 - 1.0" results in 0.10000000000000009. So how many decimal places should that be? @ARS is right, typically conversion to string functions truncate the number of decimal places to not exceed the precision that can be stored. So, maybe the to string routine truncates 0.10000000000000009 to 0.1000000, then trims the trailing zeros to give you 0.1, which may be closer to what you want. However, who's to say how many decimal places this value has. – Marlin Pierce Jul 05 '13 at 14:55
  • 1
    So in conclusion, there are numerical values, like 1/3, which have both infinite decimal places and infinite binary places, which are approximated because of finite storage. There is also precision problems introduced by converting between decimal and binary, as with 0.1. String functions round off to truncate precision beyond what the finite storage can truely represent. In these cases, the only reasonable thing to do is convert to a string, and take it's result as the number of decimal places. – Marlin Pierce Jul 10 '13 at 17:00
1

I answered this before, but I can tell from the comments that it's a little unclear. Over time I found a better way to express this.

Consider pi as

(a) 3.141592653590

This shows pi as 11 decimal places. However this was rounded to 12 decimal places, as pi, to 14 digits is

(b) 3.1415926535897932

A computer or database stores values in binary. For a single precision float, pi would be stored as

(c) 3.141592739105224609375

This is actually rounded up to the closest value that a single precision can store, just as we rounded in (a). The next lowest number a single precision can store is

(d) 3.141592502593994140625

So, when you are trying to count the number of decimal places, you are trying to find how many decimal places, after which all remaining decimals would be zero. However, since the number may need to be rounded to store it, it does not represent the correct value.

Numbers also introduce rounding error as mathematical operations are done, including converting from decimal to binary when inputting the number, and converting from binary to decimal when displaying the value.

You cannot reliably find the number of decimal places a number in a database has, because it is approximated to round it to store in a limited amount of storage. The difference between the real value, or even the exact binary value in the database will be rounded to represent it in decimal. There could always be more decimal digits which are missing from rounding, so you don't know when the zeros would have no more non-zero digits following it.

Marlin Pierce
  • 9,931
  • 4
  • 30
  • 52
1

Solution for Oracle but you got the idea. trunc() removes decimal part in Oracle.

select *
from your_table
where (your_field*1000000 - trunc(your_field*1000000)) <> 0;

The idea of the query: Will there be any decimals left after you multiply by 1 000 000.

DenisS
  • 1,637
  • 19
  • 15
1

Another way I found is

SELECT 1.110000 , LEN(PARSENAME(Cast(1.110000 as float),1)) AS Count_AFTER_DECIMAL

Kshitij Manvelikar
  • 73
  • 1
  • 1
  • 10
1

I've noticed that Kshitij Manvelikar's answer has a bug. If there are no decimal places, instead of returning 0, it returns the total number of characters in the number.

So improving upon it:

Case When (SomeNumber = Cast(SomeNumber As Integer)) Then 0 Else LEN(PARSENAME(Cast(SomeNumber as float),1)) End
sbgib
  • 5,580
  • 3
  • 19
  • 26
Tony
  • 11
  • 1
0

Here's another Oracle example. As I always warn non-Oracle users before they start screaming at me and downvoting etc... the SUBSTRING and INSTRING are ANSI SQL standard functions and can be used in any SQL. The Dual table can be replaced with any other table or created. Here's the link to SQL SERVER blog whre i copied dual table code from: http://blog.sqlauthority.com/2010/07/20/sql-server-select-from-dual-dual-equivalent/

CREATE TABLE DUAL
(
 DUMMY VARCHAR(1)
)
GO
INSERT INTO DUAL (DUMMY)
VALUES ('X')
GO

The length after dot or decimal place is returned by this query. The str can be converted to_number(str) if required. You can also get the length of the string before dot-decimal place - change code to LENGTH(SUBSTR(str, 1, dot_pos))-1 and remove +1 in INSTR part:

SELECT str, LENGTH(SUBSTR(str, dot_pos)) str_length_after_dot FROM
(
 SELECT '000.000789' as str
      , INSTR('000.000789', '.')+1 dot_pos 
   FROM dual
)
/

SQL>

STR           STR_LENGTH_AFTER_DOT
----------------------------------
000.000789    6

You already have answers and examples about casting etc...

Art
  • 5,616
  • 1
  • 20
  • 22
0

This question asks of regular SQL, but I needed a solution for SQLite. SQLite has neither a log10 function, nor a reverse string function builtin, so most of the answers here don't work. My solution is similar to Art's answer, and as a matter of fact, similar to what phan describes in the question body. It works by converting the floating point value (in SQLite, a "REAL" value) to text, and then counting the caracters after a decimal point.

For a column named "Column" from a table named "Table", the following query will produce a the count of each row's decimal places:

select
length(
    substr(
        cast(Column as text),
        instr(cast(Column as text), '.')+1
    )
) as "Column-precision" from "Table";

The code will cast the column as text, then get the index of a period (.) in the text, and fetch the substring from that point on to the end of the text. Then, it calculates the length of the result.

Remember to limit 100 if you don't want it to run for the entire table!

Database view displaying two columns; the first called "Temp", has values for temperature. The second, called "Temp-precision", has the count of each of "Temp"'s row's decimal places

It's not a perfect solution; for example, it considers "10.0" as having 1 decimal place, even if it's only a 0. However, this is actually what I needed, so it wasn't a concern to me.

Hopefully this is useful to someone :)

Matheus Avellar
  • 1,507
  • 1
  • 22
  • 29
0

Probably doesn't work well for floats, but I used this approach as a quick and dirty way to find number of significant decimal places in a decimal type in SQL Server. Last parameter of round function if not 0 indicates to truncate rather than round.

CASE 
    WHEN col = round(col, 1, 1) THEN 1
    WHEN col = round(col, 2, 1) THEN 2
    WHEN col = round(col, 3, 1) THEN 3
    ...
    ELSE null END