Using Numeric Functions to Perform Math Operations in XPath

MSXML 5.0 SDK

Microsoft XML Core Services (MSXML) 5.0 for Microsoft Office - XPath Developer's Guide

Using Numeric Functions to Perform Math Operations in XPath

XPath's numeric functions allow you to perform certain basic mathematical operations on given numeric values or node-sets.

Note that simple arithmetic—addition, subtraction, and so on—can be performed using straightforward numeric operations. For more information, see Using Numeric Operators to Perform Math Operations in XPath.

In the following table, which summarizes the numeric functions, obj? is an optional object of some arbitrary type, such as node-set or number, passed into the function; ns, a node-set; and num, an integer number.

Format Description/Example
number(obj?) Converts the argument to a number and returns the result. If the argument can't be converted, the function returns the string "NaN" (for "not a number"). If no argument is passed, the function operates on the context node.
sum(ns) Returns the sum of all nodes in the argument. If you want to simply add numeric values that aren't associated with a node-set, use the + sign as described in the Numeric operations topic.
floor(num) Returns the largest integer that is less than or equal to the argument.
ceiling(num) Returns the smallest integer that is greater than or equal to the argument.
round(num) Rounds the argument's value up or down to the nearest integer. 1.5 always rounds up to 2, and -1.5 always rounds up to -1. Accurate rounding is dependent on hardware and operating-system considerations; you should test a wide range of possible values to be sure that this function returns the results you want in your application.

number(obj?)

The following XPath expression:

number(//region[@name="Northwest"]/units)

simply returns the numeric value 465.

On the other hand, this XPath expression:

number(//region/@name)

returns the string "NaN". This can be useful when you want to perform basic arithmetic operations. If you add two elements' supposedly numeric contents, but one of them is empty or non-numeric, your results will not be what you expected. Thus you could use XSLT logic such as the following to substitute a value of 0 for any such non-numeric values:

<xsl:template match="/sales">
    <xsl:for-each select="region">
    <xsl:variable name="tax_pct">.07</xsl:variable>
    <xsl:variable name="num_sales">
        <xsl:choose>
            <xsl:when test="string(number(amount))='NaN'">0</xsl:when>
            <xsl:otherwise><xsl:value-of select="amount"/></xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <p>
        Region - <xsl:value-of select="@name"/>:<br />
        <xsl:value-of select="format-number($tax_pct * 100, '##0.00')"/>% 
        tax on amount of 
        <xsl:value-of select="format-number($num_sales, '#,##0.00')"/> is: 
        <xsl:value-of select="format-number($num_sales * $tax_pct,'#,##0.00')"/>
    </p>
    </xsl:for-each>
</xsl:template>
Note   In order to test the result of the number() function call for a value of "NaN," you must convert that result (as in this template rule) to a string, using the string() function. For more information, see Processing Text Strings by Using String Functions.

If you go back to our sample XML document and replace the value 12500.26 (the value of the Northeast region's <amount> element) with either a non-numeric value or with nothing at all, the above template rule displays in Internet Explorer as:

Region - Northeast:
7.00% tax on amount of 0.00 is: 0.00
Region - Southeast:
7.00% tax on amount of 17,692.00 is: 1,238.44
Region - Southwest:
7.00% tax on amount of 8,349.72 is: 584.48
Region - Northwest:
7.00% tax on amount of 15,239.60 is: 1,066.77

Without the use of the number() function to test for "NaN," the lines for the Northeast region look like this:

Region - Northeast:
7.00% tax on amount of NaN is: NaN

sum(ns)

To display the sum of all regions' sales amounts and units, you could use an XSLT template rule such as:

<xsl:template match="sales">
    <h4>Quarter beginning: <xsl:value-of select="@quarter"/></h4>
    <p>Total units sold by all regions:
        <xsl:value-of select="sum(.//units)"/><br />
        Total sales amounts, all regions:
        <xsl:value-of select="sum(.//amount)"/>
    </p>
</xsl:template>

The result, viewed in Internet Explorer, is:

Quarter beginning: 2001-01
Total units sold by all regions: 1512
Total sales amounts, all regions: 53781.58

floor(num) and ceiling(num)

The number of units sold by the four regions in our sample XML document are 374, 512, 161, and 425. If we wanted to construct a bar graph for these figures in some XML application, such as Scalable Vector Graphics, we may not want to set the Y-axis origin at 0, because it would diminish the (fairly slight) differences among the four regions. For example, in this case, we might want the Y-axis origin to 100—that is, the highest multiple of 100 which is less than or equal to our lowest set of values to be graphed.

Each region's "closest-and-lower multiple of 100" value can be obtained using an XPath expression such as the following:

floor(.//units div 100) * 100

Similarly, the top of the Y axis (for each region) can be established using:

ceiling(.//units div 100) * 100

round(num)

To display the values of the <amount> elements rounded to the nearest integer value, use round().

Example

This example demonstrates the round() function.

XML File (xpathfuncs.xml)

Change the href attribute in xpathfuncs.xml (shown in Sample XML Data File for XPath Functions) to reference round.xsl.

XSLT File (round.xsl)

<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="sales">
    <table border="1">
        <tr>
            <th>Region</th>
            <th>Approx Sales</th>
        </tr>
        <xsl:for-each select="region">
            <tr>
                <td><xsl:value-of select="@name"/></td>
                <td align="right"><xsl:value-of select="round(amount)"/></td>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>

</xsl:stylesheet>

Formatted Output

Region Approx Sales
Northeast 12500
Southeast 17692
Southwest 8350
Northwest 15240

Processor Output

<?xml version="1.0" encoding="UTF-16"?><table 
border="1"><tr><th>Region</th><th>Approx 
Sales</th></tr><tr><td>Northeast</td><td 
align="right">12500</td></tr><tr><td>Southeast</td><td 
align="right">17692</td></tr><tr><td>Southwest</td><td 
align="right">8350</td></tr><tr><td>Northwest</td><td 
align="right">15240</td></tr></table>