Ошибки округления и точности в XSLT 1.0

У меня есть несогласованность при использовании xsl,

вот xml,

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

и 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>

а выход -

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

но ожидаемый o/p равен,

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

Почему o/p - 523.4100000000001? как я могу получить 523.41 без округления?

Ответ 1

В XSLT 1.0 числа реализованы с двойным типом и, как и с любым двоичным типом с плавающей запятой, есть потеря точности.

В XSLT 2.0/XPath 2.0 можно использовать тип xs:decimal для работы без потери точности.


I. Решение XSLT 1.0:

Используйте format-number():

<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>

Когда это преобразование применяется к предоставленному XML-документу:

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

выдается желаемый, правильный результат:

<TotalAmount>523.41</TotalAmount>

Здесь также приведен пример, показывающий, что желаемая точность не может быть статически известна и может быть передана преобразованию в качестве внешнего/глобального параметра:

<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>

Когда это преобразование применяется к предоставленному XML-документу, создаются два результата - с точностью 2 и точностью 13:

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

II. Решение XSLT 2.0 с помощью 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>

Когда это преобразование применяется к одному и тому же XML-документу (см. выше), желаемый, правильный результат получается:

<TotalAmount>523.41</TotalAmount>