12

I have an inconsistency while using xsl,

here is the xml,

<Rate>
    <TotalRate>506.41</TotalRate>
    <TotalTax>17</TotalTax>
    <Currency>INR</Currency>
</Rate>

and xsl,

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <TotalAmount>
            <xsl:value-of select="Rate/TotalRate + Rate/TotalTax"/>
        </TotalAmount>
    </xsl:template>
</xsl:stylesheet>

and the output is,

<TotalAmount xmlns:fo="http://www.w3.org/1999/XSL/Format">523.4100000000001</TotalAmount>

but the expected o/p is,

<TotalAmount xmlns:fo="http://www.w3.org/1999/XSL/Format">523.41</TotalAmount>

Why the o/p is 523.4100000000001? how can i get 523.41 without rounding it?

C. M. Sperberg-McQueen
  • 24,596
  • 5
  • 38
  • 65
Sujit
  • 3,677
  • 9
  • 41
  • 50

1 Answers1

18

In XSLT 1.0 numbers are implemented with the double type and as with any binary floating-point type, there is a loss of precision.

In XSLT 2.0/XPath 2.0 one can use the xs:decimal type to work without loss of precision.


I. XSLT 1.0 solution:

Use the format-number() function:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="/*">
        <TotalAmount>
      <xsl:value-of select="format-number(TotalRate + TotalTax, '0.##')"/>      
        </TotalAmount>
    </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Rate>
    <TotalRate>506.41</TotalRate>
    <TotalTax>17</TotalTax>
    <Currency>INR</Currency>
</Rate>

the wanted, correct result is produced:

<TotalAmount>523.41</TotalAmount>

Here is also an example, showing that the wanted precision maynot be statically known and could be passed to the transformation as an external/global parameter:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:param name="pPrec" select="2"/>
    <xsl:param name="pPrec2" select="13"/>

    <xsl:variable name="vPict" select="'##################'"/>

    <xsl:template match="/*">
        <TotalAmount>
      <xsl:value-of select=
      "format-number(TotalRate + TotalTax,
                     concat('0.', substring($vPict,1,$pPrec))
                     )"/>
        </TotalAmount>
        <TotalAmount>
      <xsl:value-of select=
      "format-number(TotalRate + TotalTax,
                     concat('0.', substring($vPict,1,$pPrec2))
                     )"/>
        </TotalAmount>
    </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document, two results are produced -- with precision 2 and precision 13:

<TotalAmount>523.41</TotalAmount>
<TotalAmount>523.4100000000001</TotalAmount>

II. XSLT 2.0 solution using xs:decimal:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
        <TotalAmount>
      <xsl:value-of select="xs:decimal(TotalRate) + xs:decimal(TotalTax)"/>
        </TotalAmount>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the same XML document (above), the wanted, correct result is produced:

<TotalAmount>523.41</TotalAmount>
zx485
  • 28,498
  • 28
  • 50
  • 59
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • thanks, good to see your response, but what if i don't know the exact decimal place for the provided decimal number? – Sujit Oct 05 '12 at 13:03
  • @asdasd, If you don't know it, you can pass it to the transformation as a global parameter -- then some additional XSLT code can construct dynamically the wanted argument of `format-number()`. Also, see my XSLT 2.0 solution where you can use the `xs:decimal` type for exact calculations. – Dimitre Novatchev Oct 05 '12 at 13:11
  • @asdasd, Please, see my updated answer. I have added a solution when the wanted precision isn't known in advance and is passed to the transformation as a parameter. – Dimitre Novatchev Oct 05 '12 at 13:21
  • thanks, but i am using xslt 1.0. and it made me really surprised to see the result of decimal number after xsl – Sujit Oct 05 '12 at 13:28
  • 1
    Worth pointing out if you revisit that format-number is not the best to rely on for rounding, this Q&A illustrates the issue and the solution http://stackoverflow.com/questions/3805248/xsl-rounding-format-number-problem – MadMurf Jan 21 '13 at 21:51