Search code examples
javaxmlxsltxslt-2.0

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


I have an XSLT as below:

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

When I test this XSLT against the below sample file in Altova XML or in W3CSchool here, I get the result as 0.128

Sample file:

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

However, things change when I use Java to invoke the XSLT. I get the result as

<Result>0.12800000000000003</Result>

Below is the simple code I'm using:

 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")));
    }
}

Question: Why is the Java code giving the output as 0.12800000000000003? Even 0.12800000000000000 is understandable, but 0.12800000000000003 is incorrect calculation.


Solution

  • Firstly, floating point arithmetic will in general produce such rounding errors, because numbers like 0.8 cannot be represented accurately in the value space of xs:double.

    Secondly, your stylesheet is explicitly using the number() function, which converts values in the source document (like 0.8) to floating point, both in XSLT 1.0 and XSLT 2.0. XSLT 2.0 offers a solution in that you could replace the call to number() by a call on xs:decimal(), which would give you decimal arithmetic rather than binary floating-point, and thus avoid the rounding errors. But the code you are currently executing is doing floating-point arithmetic in both cases.

    The correct answer to this expression according to the rules of the W3C specification in both 1.0 and 2.0 is in fact 0.12800000000000003. The specification does not give any leniency on this. But implementors take short-cuts, and use libraries for floating-point arithmetic (and more particularly, for number-to-string conversion) that were not written to follow the W3C rules. I would strongly suspect that an implementation that outputs 0.128 for this query is using a number-to-string conversion routine that is trying to be smarter than the W3C spec allows.

    If you want to avoid this kind of rounding error, the correct approach is:

    (a) with XSLT 1.0, use format-number() to format the output to the number of decimal places that are likely to be accurate (or that are actually needed)

    (b) with XSLT 2.0, use xs:decimal arithmetic - which when you are reading numbers from the source document means explicitly making them xs:decimal, either by validating the source document against a schema that declares the type as xs:decimal, or by using the xs:decimal() function in the stylesheet.