sanjay
sanjay

Reputation: 1020

XSLT - using round() function

I have XML content like this,

<doc>
    <p>20</p>
    <p>20</p>
    <p>20</p>
    <p>100</p>
    <t>160</t>
</doc>

There are four <p> elements containing numbers, and <t> represents the total of above four <p> elements.

My objective is to calculate the percentage of these <p> elements and display the result.

This is XSL code I've written for it,

    <xsl:variable name="val1" select="abc:get-int-value(doc/p[1]/text())" as="xs:double"/>
    <xsl:variable name="val2" select="abc:get-int-value(doc/p[2]/text())" as="xs:double"/>
    <xsl:variable name="val3" select="abc:get-int-value(doc/p[3]/text())" as="xs:double"/>
    <xsl:variable name="val4" select="abc:get-int-value(doc/p[4]/text())" as="xs:double"/>
    <xsl:variable name="valTotal" select="abc:get-int-value(doc/t/text())" as="xs:double"/>

    <xsl:variable name="precentage1" select="round(($val1 div $valTotal)*100)" as="xs:double"/>
    <xsl:variable name="precentage2" select="round(($val2 div $valTotal)*100)" as="xs:double"/>
    <xsl:variable name="precentage3" select="round(($val3 div $valTotal)*100)" as="xs:double"/>
    <xsl:variable name="precentage4" select="round(($val4 div $valTotal)*100)" as="xs:double"/>

    <xsl:template match="doc">
        <xsl:value-of select="$precentage1"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="$precentage2"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="$precentage3"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="$precentage4"/>
    </xsl:template>

    <xsl:function name="abc:get-int-value" as="xs:double">
        <xsl:param name="text" as="xs:string"/>

        <xsl:analyze-string select='$text' regex='\d+'>
            <xsl:matching-substring>
                <xsl:sequence select="number(.)"/>
            </xsl:matching-substring>
        </xsl:analyze-string>
    </xsl:function>

But, it gives percentage values as, 13, 13, 13 and 63. So the total of those numbers is 102 (literally it should be 100, 99, or 101). It seems the percentage value of p[4] has been rounded up to 63 instead of 62.

Why is this happening and how can I fix this?

Upvotes: 0

Views: 7771

Answers (5)

user4188092
user4188092

Reputation:

All percentages end in .5, and so round up, making 4 x .5 = 2 extra percentage points.

Percentages are mostly used to give a rough idea of amounts relative to the whole, so making them accurate would require not rounding them at all, which is problematic if the total is a prime number more than 2 because that produces an infinite number of decimal places.

This is why a comment like The percentages will not total to 100 due to rounding. is added after tables containing percentages.

Data accuracy cannot be changed without introducing other inaccuracies. I suspect most readers will not actually check if the percentages add up to 100, and if they do, many will know why. For anybody else, either provide the disclaimer above, or let them go to some forum and get told what should be obvious when percentages are shown as integers. The long-term solution is teaching maths literacy.

Upvotes: 0

Kirill Polishchuk
Kirill Polishchuk

Reputation: 56162

That's how the round function works: it returns an integer closest in value to the argument, which means that round(62.5) = 63

You can extract precentage1, precentage2 and precentage3 from 100 and get precentage4, because all these three percent values should give you in total 100%, i.e.:

<xsl:variable name="precentage4" select="100 - $precentage1 - $precentage2 - $precentage3" as="xs:double"/>

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163262

For some discussion of algorithms for rounding a set of percentages in such a way that the total adds up to 100 (but not specifically in an XSLT context) see

How to make rounded percentages add up to 100%

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 116959

how can I fix this?

As others have already explained, you cannot "fix" this.

You can, however, alleviate the problem slightly by using round-half-to-even() instead of round(). As it happens, in the given example it will only shift the problem to the other side - i.e. the results will be 12, 12, 12, 62 with a total of 98. But on average the rounding error will be smaller.

Upvotes: 1

JLRishe
JLRishe

Reputation: 101652

why this is happening

Your expected values are wrong and don't even make sense. 20 div 160 is 12.5, and 100 div 160 is 62.5. 12.5 rounds up to 13, and 62.5 rounds up to 63, and these are the values you are seeing.

Rounding inherently produces an inexact value, so you can't expect four rounded percentages to always add up to 100.

how can I fix this

I think you will need to decide how you want to resolve this discrepancy. The round() function is working exactly how it's supposed to.

Upvotes: 1

Related Questions