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() 函数。
我有一个 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() 函数。