Rounding With Decimals

When using format-number with decimal places, there is this issue with loss of precision that leads to incorrect rounding. The problem, described in more detail e.g. here or here, is simply that when you write something like:

format-number(9.865, '#,##0.00')

... the result, in my head and in the heads of many others, should be "9.87". Often though - not always, which is the worst - it ends up being "9.86" because somewhere along the way, the original value is transformed into "9.86499999"... Apparently, the reason for this is that numbers are modeled as doubles in XSL 1.0. Honestly, I still cannot see how this would cause this rounding error when there are no operations being carried out, but hey.

The function below attempts to solve the issue at hand:

<!--
 - Problem is that decimal numbers in XSL 1.0 are modelled as
 - doubles meaning that loss of precision occurrs frequently.
 - 
 - Often, when using `format-number`, a value such as "9.825"
 - formatted according to a pattern "0.00" results in "9.82"
 - instead of "9.83".
 - 
 - This functions rectifies the problem by increasing
 - the relevant decimal when necessary & applicable.
-->

<xsl:function name="locals:preformatNumber">
  <xsl:param name="number" />
  <xsl:param name="decimals" />

  <xsl:choose>
    <!-- return "0" for empty values -->
    <xsl:when test="string-length(string($number)) = 0">
      <xsl:value-of select="0" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:choose>
        <!-- no decimal point, simply return the number passed in -->
        <xsl:when test="not(contains(string($number), '.'))">
          <xsl:value-of select="$number" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="complete" select="number($number)" />
          <xsl:variable name="before"
            select="substring-before(string($complete), '.')" />
          <xsl:variable name="after"
            select="substring-after(string($complete), '.')" />

          <xsl:choose>
            <xsl:when test="string-length($after) &gt; $decimals">
              <!-- the displayed decimals -->
              <xsl:variable name="displayed"
                select="substring($after, 1, $decimals)" />
              <!-- the decimal that maybe needs incrementing -->
              <xsl:variable name="relevant"
                select="substring($after, $decimals + 1, 1)" />
              <xsl:variable name="newRelevant"
                select="utils:ifElse(string($relevant) = '5', 6, $relevant)" />
              <!-- any potential remaining decimals -->
              <xsl:variable name="remaining" select="substring(
                $after, $decimals + 2, string-length($after) - $decimals - 1)" />

              <xsl:value-of
                select="concat($before, '.', $displayed, $newRelevant, $remaining)"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="$number" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<xsl:function name="locals:formatNumber">
  <xsl:param name="number" />
  <xsl:value-of select="format-number(
    locals:preformatNumber($number, 2),
    '#,##0.00',
    'format')" />
</xsl:function>

results matching ""

    No results matching ""