2

I'm working on a piece ColdFusion code to calculate the grade point average. How do I format (Round up) the GPA to one decimal place?

I tried using numberFormat but I did not get the result as expected. The GPA was being rounded to the nearest whole number. Ex. when "I have GPA 3.23, the function would round it up to 3.0, instead of 3.2.

<cfdump var = "#numberFormat(totalgpa, '.0')#">

Ex.

When I have GPA 3.23, the expected result should be 3.2;

When I have GPA 3.45, the expected result should be 3.5;

When I have GPA 3.98, the expected result should be 4.0;

James A Mohler
  • 11,060
  • 15
  • 46
  • 72
dkgcb
  • 81
  • 1
  • 2
  • 9
  • What version of ColdFusion? – Shawn Feb 06 '19 at 23:13
  • 1
    This appears to work correctly on CF10-2018 and Lucee. `numberFormat(3.98,'.0')` == `4.0` https://cffiddle.org/app/file?filepath=151f84a9-568f-4180-84f1-bc439eb9e39d/f86dfd0a-ed65-426f-b2d8-e6e05e513e9d/2e8f0410-e3b6-4c4f-9463-85d174b95162.cfm – Shawn Feb 06 '19 at 23:20
  • What other code is in your process that may be interfering? – Shawn Feb 06 '19 at 23:24
  • This is ColdFusion 2018 – dkgcb Feb 07 '19 at 13:19
  • I've used the blank cfm page to do some testing with number format with same results. – dkgcb Feb 07 '19 at 13:41
  • Same results as in... the wrong number? It would help others with the same problem if you posted an example showing the wrong result using either cffiddle or trycf.com. **EDIT** Also, when you say "celing", that implies *always* round up, but it sounds like you actually want to round up when the value is >= 5 – SOS Feb 07 '19 at 15:42
  • 2
    @dkgcb The same incorrect results? Please share the code that you're using. Is it different than the cffiddle I posted above? – Shawn Feb 07 '19 at 16:16

1 Answers1

1

I stopped trusting CF when it comes to rounding and number precision. Here is "the Java way":

<cfoutput>
    #roundWithScale(3.23, 1)# = 3.2<br>
    #roundWithScale(3.45, 1)# = 3.5<br>
    #roundWithScale(3.98, 1)# = 4.0<br>
</cfoutput>

<cffunction name="roundWithScale" access="public" output="false" returnType="numeric">

    <cfargument name="value"    type="numeric"  required="true">
    <cfargument name="scale"    type="numeric"  default="2">
    <cfargument name="rounding" type="string"   default="ROUND_HALF_UP">

    <cfset LOCAL.BigDecimal = createObject("java", "java.math.BigDecimal")>

    <cfset LOCAL.value = createObject("java", "java.math.BigDecimal").init(
        toString(ARGUMENTS.value)
    )>

    <cfreturn LOCAL.value.setScale(
        javaCast("int", ARGUMENTS.scale),
        LOCAL.BigDecimal[ARGUMENTS.rounding]
    )>
</cffunction>
Alex
  • 7,743
  • 1
  • 18
  • 38
  • I agree with Alex. CF primarily works with doubles (less precise) and often has a creative interpretation of numbers ;-) Better to use BigDecimal when precision is important. – SOS Feb 07 '19 at 15:39
  • Haha! I finally found something that @Ageax and I aren't in sync over. I think this may be overkill for just one decimal place. The reason for the precision errors is because of the underlying floating point conversions, but that won't really come into play until you're working with several decimal places. – Shawn Feb 07 '19 at 16:16
  • Say it isn't so! ;-) Seriously though, while it's *possible* it may be overkill, knowing CF's liberal interpretation of numbers and dates - I'm very hesitant to rely on it's black box functions to produce consistent results - in all scenarios. Especially without any example of the numbers and data types involved. Since it's entirely possible the real values would show there's an edge case involved here that's producing one of CF's "weird" results. Not to mention, relying on code that "will only work" under conditions X rubs me the wrong way ;-) – SOS Feb 07 '19 at 17:25
  • ... ;-) Unless it causes a major performance problem, my preference is to stick with BigDecimal, as it's less brittle and more robust anyway. – SOS Feb 07 '19 at 17:32
  • @Ageax It is more robust, but in this instance, we're talking about only two decimal places. That is _well_ within the capabilities of just about any numbering system. My guess is that OP may be doing some odd math that is resulting in some huge decimal (or repeating decimal) that results in a huge floating-point number that rounds like it's supposed to, but may give an unexpected result. OP said they'd done testing, but I'd still like to see input numbers. Formatting to 1 decimal place should have any issues with a proper number. – Shawn Feb 08 '19 at 02:43
  • Rather than "huge number", I actually meant "lotsa digits after decimal". :-) Floating-point calculations can seem weird if you aren't expecting them. And they really shouldn't be allowed to come into play for just 2 significant decimal digits. – Shawn Feb 08 '19 at 02:46
  • @Shawn - Yeah, but that is kind of the point. It *should* work as expected, but...we all know it doesn't always ;-). So rather than worrying about "is this a special case or not" or "will it still work if x changes to y", it is simpler to just use a core tool that produces the expected results every time. – SOS Feb 08 '19 at 03:58
  • @Ageax But that's kinda my issue. Normally I'm all for leveraging the build-in Java functionality, like String methods. But this isn't built-in. This requires instantiating a Java object. And again, since we're only talking about 2 decimal places, we may not be gaining a lot or really improving accuracy. I really would like to see the Op's actual values and how they are derived and how they are coming up different than expected. CF's `numberFormat()` shouldn't get anywhere close to a precision-point that would affect that small number of digits. – Shawn Feb 08 '19 at 04:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188089/discussion-between-shawn-and-ageax). – Shawn Feb 08 '19 at 05:20