October 10, 2006 at 8:49 am
i'm trying to count the number of decimal places in a field.
e.g. mynumber decimal 9 (18,9)
Len(mynumber) result = 11
I've tried converting it to a string Len(STR(mynumber)) result = 10
What I really want is a count of the number of digits following the decimal point ignoring the trailing zeros
e.g. 0.8333 result should be 4
0.99 result should be 2
Any ideas - Thanks
October 10, 2006 at 9:37 am
Forgot about no Decimal places:
DECLARE @D DECIMAL(18,9)
,@S VARCHAR(20)
,@R VARCHAR(20)
,@Pos SMALLINT
SET @D = 0.0
SET @s-2 = CAST(@D AS VARCHAR(20))
SET @r = REVERSE(SUBSTRING(@S, CHARINDEX('.', @s-2) + 1, 20))
SET @Pos = PATINDEX('%[1-9]%' , @r)
IF @Pos = 0
SELECT 0
ELSE
SELECT LEN(SUBSTRING(@R, @Pos, 20))
October 10, 2006 at 10:17 am
For what it's worth, here is a set-based approach. It requires a numbers table though.
IF EXISTS ( SELECT 1 FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[CountDP]') AND xtype IN ( N'FN', N'IF', N'TF' ) ) BEGIN DROP FUNCTION [dbo].[CountDP] END GO CREATE FUNCTION [dbo].[CountDP] ( @decNumber DECIMAL(18, 9) ) RETURNS TINYINT AS BEGIN /******************************************************************************************************* * dbo.CountDP * * Usage: print dbo.countdp(10.0000001) -- 7 print dbo.countdp(10) -- 0 print dbo.countdp(10.000) -- 0 print dbo.countdp(0) -- 0 print dbo.countdp(0.1234567) -- 7 print dbo.countdp(null) -- null print dbo.countdp() --ERROR * * Modifications: * Developer Name Date Brief description * ------------------- ----------- ------------------------------------------------------------ * ********************************************************************************************************/ DECLARE @DecCount TINYINT SELECT @DecCount = ( SELECT ISNULL(MAX(num), N.Num) FROM Numbers WHERE Num N.Num AND SUBSTRING(CAST(@decNumber AS VARCHAR), Num, 1) NOT IN ( '0', '' ) ) - ( Num ) FROM Numbers N WHERE Num < LEN(CAST(@decNumber AS VARCHAR(18))) AND SUBSTRING(CAST(@decNumber AS VARCHAR), Num, 1) = '.' RETURN @DecCount END GO
SQL guy and Houston Magician
October 10, 2006 at 3:35 pm
This function is about 100 times faster:
IF EXISTS (SELECT * FROM sysobjects WHERE name = N'DecimalPlaces')
DROP FUNCTION DecimalPlaces
GO
CREATE FUNCTION dbo.DecimalPlaces
(@A float)
RETURNS tinyint
AS
BEGIN
declare @r tinyint
IF @a IS NULL
RETURN NULL
set @r = 0
while @a - str(@A, 18 + @r, @r) <> 0
begin
end
RETURN @r
END
GO
_____________
Code for TallyGenerator
October 10, 2006 at 7:18 pm
No, it's actually much worse.
I tested my function against 16540 row table.
It returned result in 3..5 seconds (I'm not alone on that server )
Than I started same query but using function [dbo].[CountDP].
I's been 2 hours 50 minutes since then, it's still going.
So, there is a reminder: avoid referencing tables inside UDF!
Even if it's such "set based" table as Numbers.
_____________
Code for TallyGenerator
October 11, 2006 at 1:51 am
Old guys rule... 1 million conversions... 11 seconds... works for positive numbers, negative numbers, zero, and NULL...
DECLARE @Places INT
SELECT TOP 1000000 @Places = FLOOR(LOG10(REVERSE(ABS(SomeNumber)+1)))+1
FROM dbo.BigTest
--Jeff Moden
Change is inevitable... Change for the better is not.
October 24, 2006 at 11:34 pm
Ouch! I guess next time I should get more information on how a solution might be used before posting a response!
Interesting solutions all around, I thought. Very slick guys!
SQL guy and Houston Magician
October 25, 2006 at 1:41 am
Jeff,
are you sure the code you posted works? I tried it out of curiosity, and I'm getting some strange results... e.g. for number 99, depending on how I enter it, result is either 1 or -2.
DECLARE @Places INT
SELECT @Places = FLOOR(LOG10(REVERSE(ABS(cast (99 as float))+1)))+1
SELECT @places
-----------
1
(1 row(s) affected)
DECLARE @Places INT
SELECT @Places = FLOOR(LOG10(REVERSE(ABS(99.0000)+1)))+1
SELECT @places
-----------
-2
(1 row(s) affected)
October 25, 2006 at 5:41 pm
There would certainly appear to be a fly in the ointment ... thanks for the catch and sorry for the mistake folks... I'll see if I can fix it... Maybe I meant "Old guys drool"
--Jeff Moden
Change is inevitable... Change for the better is not.
October 25, 2006 at 6:57 pm
Ok... this takes 2 seconds longer (1,000,000 rows in 23 seconds instead of 21)... had to work around the "zero domain" on the LOG10 function to get this to work properly for whole numbers...
DECLARE @TestNum DECIMAL(38,15)
SET @TestNum = 99 --99.0000
SELECT CASE
WHEN FLOOR(REVERSE(ABS(@TestNum))) = 0.0
THEN 0
ELSE FLOOR(LOG10(REVERSE(ABS(@TestNum)))+1)
END
It does NOT support FLOAT... (the original problem description was based on the DECIMAL datatype so I think we're ok there)... Float does wierd things when you throw a REVERSE on it. If anyone needs a decimal place counter that works on FLOAT, we'll have to take a different tact...
--Jeff Moden
Change is inevitable... Change for the better is not.
October 26, 2006 at 7:46 am
10-PATINDEX('%[^0]%',REVERSE(RIGHT(CAST(mynumber as varchar),9))+'1')
1,000,000 rows <=10 secs
Far away is close at hand in the images of elsewhere.
Anon.
October 26, 2006 at 5:20 pm
Very cool, David... fast as all get out... but try this... obviously, we have to know exactly what the scale of the decimal places is to use it or we come up with the wrong answer...
DECLARE @TestNum DECIMAL(38,15)
SET @TestNum = 99.123456789012345
SELECT 10-PATINDEX('%[^0]%',REVERSE(RIGHT(CAST(@TestNum as varchar),9))+'1')
However... you gave me one heck of an idea... the following takes a bit more time (1,000,000 records in about 12 seconds)...
DECLARE @TestNum DECIMAL(38,15)
SET @TestNum = 90 --99.123456780000000 --0.123456780000000 --99.1 --90
SELECT CHARINDEX('.',REVERSE(@TestNum))
-PATINDEX('%[^0]%',REVERSE(@TestNum))
...the neat thing about it is that you don't need to know the precision or scale of the decimal column... it figures it out...
--Jeff Moden
Change is inevitable... Change for the better is not.
February 5, 2013 at 3:06 am
Slight improvement that works for decimal datatype:
CASE WHEN FLOOR(LOG10(REVERSE(ABS(Wert)+1)))+1 < 0 THEN 0 ELSE FLOOR(LOG10(REVERSE(ABS(Wert)+1)))+1 END
February 5, 2013 at 7:52 am
Markus S. Gallagher (2/5/2013)
Slight improvement that works for decimal datatype:CASE WHEN FLOOR(LOG10(REVERSE(ABS(Wert)+1)))+1 < 0 THEN 0 ELSE FLOOR(LOG10(REVERSE(ABS(Wert)+1)))+1 END
Slight improvement how? Is it faster?
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 15 posts - 1 through 15 (of 30 total)
You must be logged in to reply to this topic. Login to reply