这是一个关于精度,舍入,数据类型以及在XSLT 2.0中使用xs:integer()函数的问题。我们有以下代码行:
<xsl:value-of select="xs:integer(round(InvoiceAmount * 100))"/>
当源XML中InvoiceAmount的值为-62.84时,value-of的结果为-6283,这是不正确的。我们已经找到了解决这个问题的方法,但我们真的需要知道这里发生了什么。
认为这是一个二进制乘法的错误,其值偏离了一小部分,我们尝试使用格式数减少到32位小数,但是没有显示任何内容(返回-6284.0 with 0到32位):
<xsl:value-of select="format-number(InvoiceAmount * 100,'0.00000000000000000000000000000000')"/>
此外,如果我们通过硬编码-62.84替换XSL中的InvoiceAmount,它会给出正确的结果(-6284)。如果我们不使用xs:integer()转换为整数,它也会给出正确的结果。如果我们使用xs:decimal()而不是xs:integer()它可以工作。如果我们立即将XML值转换为十进制(请参阅以下代码),它可以工作:
<xsl:value-of select="xs:integer(round(xs:decimal(InvoiceAmount) * 100))"/>
我不知道它默认使用什么数据类型,但是一旦它知道InvoiceAmount是小数就可以了。所以,我们有办法解决这个问题,但是我们想知道它假设的是InvoiceAmount的数据类型,然后为什么乘以100,舍入和转换为整数会导致问题。
谢谢,
史蒂夫
答案 0 :(得分:2)
假设没有涉及模式,则InvoiceAmount元素的类型值为xs:untypedAtomic - 这实际上意味着一个字符串,但它自身适应使用它的上下文。如果您显式转换为xs:decimal,则untypedAtomic值“-62.84”将完全转换。如果你没有进行显式转换,那么当你乘以100时,该值将被视为xs:double,并且没有xs:double值是完全-62.84,因此它必须找到最近的双精度值
可以在此处找到从xs:untypedAtomic到xs:string的转换的XSLT 2.0规则:
http://www.w3.org/TR/xpath-functions/#casting-from-strings
反过来又遵循XML Schema 1.0 Part 2中的规则,可以在这里找到:
http://www.w3.org/TR/xmlschema-2/#double
并且相关规则是
“词汇空间中的文字·表示十进制数d”映射到最接近d的double的值空间中的归一化值;如果d正好在两个这样的值之间,那么偶数值是这是d的最佳近似值([Clinger,WD(1990)],[Gay,DM(1990)]),它比[IEEE 754-1985]所要求的映射更精确。“
现在,这为实现带来了一些问题。 Clinger和Gay给出的用于找到最接近的xs:double值的算法非常复杂,并且许多产品可能通过调用其方便的编程语言库提供的字符串到双重转换来实现快捷方式,这可能更快但不太准确。
XSD 1.1承认在这一方面失败:“由于IEEE允许在值的四舍五入中有所变化,符合本规范的处理器可能会在它们的词汇映射中表现出一些变化。”但是那个发布日期的XSLT 2.0,所以一个严格符合XSLT 2.0的处理器必须跟随Clinger / Gay。Saxon通过以下方法实现字符串到双重转换:(a)检查Java允许的一些内容,XPath不支持(例如十六进制数字),然后(b)调用Double.parseDouble()。当应用于字符串“-62.84”时,其效果是产生一个双精度数,其精确数值为-62.840000000000003410605131648480892181396484375。这是最接近-62.84的双倍?我有一些工具可以检查这个,但我不认为它们在我今天使用的机器上。
在双算术中将此乘以100会得到一个完全符合-6284的值。
由于round()舍入到最接近的整数,并且我们有一个完全等于整数或至少非常接近的值,我无法看到任何一致的实现如何产生-6283。这并不是说该值接近两个整数之间的中点,在更宽松的XSD 1.1规则下允许一些变化。