XSLT 与 javax.xml.transform (0.2*0.8*0.8) 的不正确乘法结果

Incorrect multiplication result by XSLT with javax.xml.transform (0.2*0.8*0.8)

我有一个 XSLT 如下:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>

<xsl:template match="Test">

        <Result>
            <xsl:value-of select="number(depth)*number(width)*number(height)"/>
        </Result>


</xsl:template>

当我在 Altova XML 或 W3CSchool here 中针对以下示例文件测试此 XSLT 时,我得到的结果为 0.128

示例文件:

<?xml version="1.0" encoding="UTF-8"?>
<Test>
<depth>.8</depth>
<width>.8</width>
<height>.2</height>
</Test>

但是,当我使用 Java 调用 XSLT 时,情况发生了变化。我得到的结果是

<Result>0.12800000000000003</Result>

下面是我使用的简单代码:

 import javax.xml.transform.*;
    import javax.xml.transform.stream.StreamResult;
    import javax.xml.transform.stream.StreamSource;
    import java.io.File;
    import java.io.IOException;
    import java.net.URISyntaxException;

public class TestMain {
    public static void main(String[] args) throws IOException, URISyntaxException, TransformerException {
        TransformerFactory factory = TransformerFactory.newInstance();
        Source xslt = new StreamSource(new File("transform.xslt"));
        Transformer transformer = factory.newTransformer(xslt);

        Source text = new StreamSource(new File("input.xml"));
        transformer.transform(text, new StreamResult(new File("output.xml")));
    }
}

问题:为什么 Java 代码给出的输出为 0.12800000000000003? 连0.12800000000000000都可以理解,但是0.12800000000000003是不正确的计算。

首先,浮点运算一般会产生这样的舍入误差,因为0.8这样的数字在xs:double的值space中不能准确表示。

其次,您的样式表明确使用了 number() 函数,该函数在 XSLT 1.0 和 XSLT 2.0 中将源文档中的值(如 0.8)转换为浮点数。 XSLT 2.0 提供了一种解决方案,您可以将对 number() 的调用替换为对 xs:decimal() 的调用,这将为您提供十进制算术而不是二进制浮点数,从而避免舍入错误。但是您当前正在执行的代码在这两种情况下都在进行浮点运算。

根据 1.0 和 2.0 中 W3C 规范的规则,此表达式的正确答案实际上是 0.128000000000000003。规范对此没有任何宽大处理。但是实现者会走捷径,使用未按照 W3C 规则编写的浮点运算库(更具体地说,用于数字到字符串的转换)。我强烈怀疑为此查询输出 0.128 的实现正在使用数字到字符串的转换例程,该例程试图比 W3C 规范允许的更智能。

如果想避免这种舍入误差,正确的做法是:

(a) 对于 XSLT 1.0,使用 format-number() 将输出格式化为可能准确(或实际需要)的小数位数

(b) 对于 XSLT 2.0,使用 xs:decimal 算术 - 当您从源文档中读取数字时,这意味着明确地使它们 xs:decimal,或者通过验证源文档的模式将类型声明为 xs:decimal,或使用样式表中的 xs:decimal() 函数。