Getting The Decimal Part Of A Number In ColdFusion
Over the weekend, I was fooling around with ColdFusion date/time values when I thought it would be useful to grab the decimal value of the date (the time part). Then it occurred to me  I don't think I have ever done this before. Now, I don't mean grabbing the actual fraction, I mean getting the integer value that is right of the decimal point.
After some good pacing, the only way to do this I could come up with through ColdFusion string manipulation. Since ColdFusion stores all simple values as strings, this makes manipulating values as strings quite natural. But, even when it came to string manipulation, I found two different ways to do this: one way uses a regular expression to strip out the decimal (and leading zero), the other way treats the value as a decimaldelimited list.
Here is the regular expression version:
 <cffunction
 name="GetDecimal"
 access="public"
 returntype="numeric"
 output="false"
 hint="Returns the decimal value of the given number (as an integer).">

 <! Define arguments. >
 <cfargument
 name="Value"
 type="numeric"
 required="true"
 />

 <!
 Once we have the numeric decimal value, we can
 convert it to a string so that we can use a regular
 expression replace to strip out the starting zero
 and decimal point.
 >
 <cfreturn
 ToString(
 ARGUMENTS.Value  Fix( ARGUMENTS.Value )
 ).ReplaceFirst(
 "^0?\.",
 ""
 )
 />
 </cffunction>
In this version, I am getting the decimal value but subtracting the fix()'d value from the actual value. This should result in a zeroleading decimal value. Then, I convert it to a string (just in case) and strip out the leading zero and decimal point. This results in the integer form of the decimal:
 <! Get decimal values values. >
 #GetDecimal( 134.5464 )#
 #GetDecimal( 134 )#
 #GetDecimal( 134.34464 )#
 #GetDecimal( 134.45444554445 )#
... results in:
5464
0
34464
45444554445
Notice that the second value, 134, results in the decimal value, 0.
In this next version, we treat the value as a decimalpointdelimited list. By doing that, we can just grab the last list item to get the decimal value as an integer:
 <cffunction
 name="GetDecimal2"
 access="public"
 returntype="numeric"
 output="false"
 hint="Returns the decimal value of the given number (as an integer).">

 <! Define arguments. >
 <cfargument
 name="Value"
 type="numeric"
 required="true"
 />

 <!
 To get the decimal value, we are going to treate the
 value as a list with a decimalpoint delimiter. When
 doing this, we will append our own value to make sure
 that the list has at least TWO (maybe three if there is
 already a decimal place) list items.
 >
 <cfreturn
 ListGetAt( (ARGUMENTS.Value & ".0"), 2, "." )
 />
 </cffunction>
Ok, so now we have two different approaches, which do we use? Time for some speed tests. Now, I know that I get a lot of crap for running CFLoop speed tests (not true load bearing speed tests), but hey come on, I don't get enough sleep for that sort of thing :) Take it with a grain of salt if you like.
 <! Test the ColdFusion regular expression method. >
 <cftimer
 type="OUTLINE"
 label="Partial Math Method And RegEx">

 <cfloop
 index="intI"
 from="1"
 to="1000"
 step="1">

 <!
 Get a random decimal value to make sure
 that ColdFusion is not just optimizing
 the compiled code.
 >
 <cfset intDecimal = GetDecimal(
 "1." &
 RandRange( 1, 9 )
 ) />

 </cfloop>

 </cftimer>


 <! Test the decimaldelimitedlist method. >
 <cftimer
 type="OUTLINE"
 label="Delimited List Method">

 <cfloop
 index="intI"
 from="1"
 to="1000"
 step="1">

 <!
 Get a random decimal value to make sure
 that ColdFusion is not just optimizing
 the compiled code.
 >
 <cfset intDecimal = GetDecimal2(
 "1." &
 RandRange( 1, 9 )
 ) />

 </cfloop>

 </cftimer>
Running the above speed tests, the delimited list method ran 2 to 3 times faster than the regular expression method (2347ms vs. 74123ms). Thinking about it, after the fact, this makes a lot of sense. The list method uses no math and no regular expressions; it just parses the number as a string and grabs part of its value. There is very easy to do. In the first version, I am actually doing math. No wonder it is slower.
Reader Comments
I didn't do speed tests, but why not this?
<cfset number = 3.14159 />
<cfset decimal = number  int(number) />
<cfdump var="#decimal#">
@Ryan,
That gives me:
0.14159
... what I wanted to get was just
14159
But this is basically doing the same thing as my Fix() method. Int() itself makes me a little nervous because it actually rounds, where as Fix() just strips off the decimal.
hi try using listLast() a with "." as the delimiter.
Good suggestion. ListGetAt(... 2), in this case is the same as ListLast()... but ListLast() is less typing :)
Oh wait, no, it;s not the same thing (which is why I didn't use it). Think of this scenario:
4
I append ".0" to that to get:
4.0
ListGetAt(.. 2) in this case is the same as ListLast(). However, if my initial value is:
4.3
... and then I append ".0" to get:
4.3.0,
... then ListGetAt( ... 2) will give me "3", while ListLast() will get me "0".
I need to get ListGetAt() to deal with cases where I have a decimal point already vs. cases where I am dealing with a whole number.
You may find this a little more straightforward:
#val(listRest(ARGUMENTS.Value, "."))#
@Nelson,
Not a bad idea. Thanks.
Why not use REVERSE(FIX(REVERSE(num)))?