XSLT 1.0中的数字四舍五入和精度问题

12
我在使用xsl时遇到了不一致的问题,
这里是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>

"输出结果为:"
<TotalAmount xmlns:fo="http://www.w3.org/1999/XSL/Format">523.4100000000001</TotalAmount>

但是预期的输出为,
<TotalAmount xmlns:fo="http://www.w3.org/1999/XSL/Format">523.41</TotalAmount>

为什么输出值是523.4100000000001?如何在不四舍五入的情况下得到523.41?
1个回答

18
在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. 使用xs:decimal的XSLT 2.0解决方案:

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

谢谢,很高兴看到你的回复,但是如果我不知道所提供的十进制数的确切小数位呢? - Sujit
如果您不知道它,可以将其作为全局参数传递给转换-然后,一些额外的XSLT代码可以动态构建所需的 format-number() 参数。此外,请参阅我的XSLT 2.0解决方案,其中您可以使用 xs:decimal 类型进行精确计算。 - Dimitre Novatchev
@asdasd,请查看我的更新答案。我添加了一个解决方案,当所需精度事先未知并作为参数传递给转换时。 - Dimitre Novatchev
谢谢,但我正在使用XSLT 1.0。看到XSL后小数的结果让我感到非常惊讶。 - Sujit
1
值得指出的是,如果您重新访问该格式化-number函数,它并不是可靠的四舍五入方法。这个问题和解决方案在这个问答中有所说明:https://dev59.com/2m865IYBdhLWcg3wceRU - MadMurf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接