Number rounding and precision problems in XSLT 1.0
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>