ColdFusion round() function bug when operation performed on argument

UPDATE (July 15, 2009): This "bug" still exists in Adobe ColdFusion 9 beta 1. It also seems to behave the same in OpenBD 1.1. This bug does not seem to exist in Railo (I can't reproduce any similar problems in Railo 3.0 or Railo 3.1). For example, round(4.0005*1000) outputs 4000 from both AdobeCF and OpenBD while Railo outputs the expected 4001. There may very well be other scenarios where a floating point representation causes similar unexpected results in Railo as well, but I have not been able to find it yet (nor have I performed an absolutely thorough test, maybe soon if time permits).

Have you ever used a CFML trick like this to round a number to the nearest hundredth?

<cfoutput>#round(someNumber*100)/100#</cfoutput>

I have, and I never had a problem, until...

<cfscript>
    valueA = 4000.5;
    valueB = 4.0005*1000;
    roundedA = round(valueA);
    roundedB = round(valueB);
</cfscript>

You'd be perfectly sane to expect the first two variables to output as 4000.5 and the second two (rounded) to output 4001. Unfortunately, roundedB is output as 4000, not 4001!

This definitely appears to be a bug, but I do have a rather simple workaround...

It appears that, despite the output of both valueA and valueB being the same, the ColdFusion engine must be storing references to two different Java values (possibly a different Java type at that point?), which do not represent the same value.

The comments on the Adobe livedocs page for the CF8 round() function (http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_m-r_40.html) suggest that the CF engineers are simply using round() from java.lang.Math, and sure enough, I get the same output as above with this code:

<cfscript>
    valueA = 4000.5;
    valueB = 4.0005*1000;
    roundedA = createObject('java', 'java.lang.Math').round(valueA);
    roundedB = createObject('java', 'java.lang.Math').round(valueB);
</cfscript>

When I first discovered this issue, I assumed it was a fluke and asked my buddy Brian Hegeman to test it out on his laptop (thanks again, B). Sure enough, same results. So we both got to messing around with javaCast() and along the way discovered that a javaCast to double produces the same (incorrect) results for the above example, but javaCast to float gives us what we want:

<cfoutput>
    #round( javaCast('float', 4.0005*1000) )#
</cfoutput>

So we seem to have a workaround. I even wrote a script to loop over a whole lot of integers to test and it seems solid, but a little part of me wondered if there may be other similar or new issues brought to light by the javaCast to float. The solution I settled on is to javaCast to string inside the round() function. This may seem odd, but this essentially "erases" any gunked up typing or odd references stored under the hood -- I'm seeing the proper output before calling a round() function, so why not force ColdFusion to store a reference to exactly what is output, then carry on? ColdFusion allows for this dynamic typing, so it's no problem to pass a string as a numeric, so long as it can be represented as a numeric.

With that, I whipped up the following roundBetter() UDF to work around this bug. While I was at it I also allowed for a second (optional) argument to allow me to specify a scale (decimal place) to round to, rather than rounding only to a whole number.

<cffunction name="roundBetter" returntype="numeric" access="public" output="false">
    <cfargument name="toRound" type="numeric" required="true" />
    <cfargument name="scale" type="numeric" required="false" default="0"
                hint="Scale to round to (number of decimal places)." />


    <cfscript>
        var result = arguments.toRound;
        var scaleMultiplier = 10^arguments.scale;

        result = result * scaleMultiplier;
        result = round(javaCast('string', result) );
        result = javaCast('string', result/scaleMultiplier);

        return result;
    
</cfscript>
</cffunction>

I'll do my best to report this to Adobe next, though I don't believe they've made their bug tracker public just yet, so I'll probably have to use the generic Feature Request/Bug Report form (http://www.adobe.com/cfusion/mmform/index.cfm?name=wishform).

Has anyone else come across this bug? I did a good bit of searching and didn't turn up any reports, but I'm curious to know if others have stumbled upon this.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Ben M's Gravatar I hope Allairemadobe is looking at this blog. You're their top QA guy!
# Posted By Ben M | 2/17/09 10:28 PM
Walter Albrecht's Gravatar I have had the same problem (causing major problem for me in an app working to 2 and 3 decimal places). Even though javacast works in your example
fails in the following situation :

<CFSET A = 11.283>
<CFSET B = 1066.27>
<CFSET C = A * B>
<CFSET D = C * 100>
<CFSET E = Round(JavaCast("float", D))>
<CFOUTPUT>#E#</CFOUTPUT>
CF Answer : 1203073
Should be : 1203072

I have contacted Adobe on this issue, and they have opened a bug. The bug number is 75688 (Not sure how to monitor the progress).

I will try your solution and see if it works.
# Posted By Walter Albrecht | 3/11/09 11:28 AM
Jamie Krug's Gravatar @Walter,

Your example confirms my hunch about casting to float being dangerous. I knew it fixed one particular issue, but wasn't confident that it would fix all use cases. This is precisely why I decided to go with a javaCast to string in my roundBetter() UDF -- it takes advantage of CF's dynamic nature and simply causes CF to "forget" about any estimated typing issues.

Using your example, the following (or my UDF) will work for you:

#round( javaCast('string', 11.283*1066.27*100) )#

Best,
Jamie
# Posted By Jamie Krug | 3/11/09 12:15 PM
Curt Gratz's Gravatar Wow, thanks for writting this post. I thought I was going crazy for a bit there.
# Posted By Curt Gratz | 9/25/09 2:06 PM
# Posted By Hemant | 6/12/11 11:48 PM
Jamie Krug's Gravatar @Hemant: I had no idea the precisionEvaluate() function existed. Thanks for the tip!
# Posted By Jamie Krug | 6/13/11 9:22 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002. Contact Blog Owner