Recipe 3.2. Rounding Numbers to a Specified Precision
Problem
You want to round to a specific
number of decimal places; however, XSLT's
round, ceiling, and
floor functions always map numbers to integer
values.
Solution
XSLT 1.0
Multiply, round, and divide using a power of ten that determines how
many decimal digits are required. Assuming $pi =
3.1415926535897932:
<xsl:value-of select="round($pi * 10000) div 10000"/>
results in 3.1416. Similarily:
<xsl:value-of select="ceiling($pi * 10000) div 10000"/>
results in 3.1416, and:
<xsl:value-of select="floor($pi * 10000) div 10000"/>
results in 3.1415.
Rounding to a specific number of decimal places is also achieved
using
format-number():
<xsl:value-of select="format-number($pi,'#.####')"/>
This results in 3.1416. This will work even if more than one
significant digit is in the whole part because
format-number never uses a format specification as
an indication to remove significant digits from the whole part:
<xsl:value-of select="format-number($pi * 100,'#.####')"/>
This results in 314.1593.
You can use format-number to get the effect of
truncating rather than rounding by using one more formatting digit
than required and then chopping off the last character:
<xsl:variable name="pi-to-5-sig" select="format-number($pi,'#.#####')"/>
<xsl:value-of select="substring($pi-to-5-sig,1,string-length($pi-to-5-sig) -1)"/>
This results in 3.1415.
XSLT 2.0
The new XPath 2.0 round-half-to-even( ) function
will do the trick for most applications. The half to
even rule means that when the value being rounded is smack
between the lower and higher values, then rounding will be in the
direction that produces an even result. So
round-half-to-even(1.115,2) eq 1.12 and
round-half-to-even(1.125,2) eq 1.12 also! In the
first case, we round up because 2 is even, and the second rounds down
because 3 is not. The theory behind this is that if you have a bunch
of numbers, you should roughly round up as often as you round down so
rounding errors cancel out. As you probably guessed, the second
argument is the number of decimal places to round to.
If your application mandates that 5 in the last decimal place always
rounds upward, you can use the techniques employed in the XSLT 1.0
recipe.
Discussion
The multiply, round, and divide technique works well as long as the
numbers involved remain within the representational limits of
IEEE floating point. If you try to capture
too many places after the decimal, then the rules of IEEE floating
point will interfere with the expected result. For example, trying to
pick up 16 decimal digits of pi will give you only 15:
<xsl:value-of select="round($pi * 10000000000000000) div 10000000000000000"/>
This results in 3.141592653589793, not 3.1415926535897932.
An alternative technique manipulates the number as a string and
truncates:
<xsl:value-of select="concat(substring-before($pi,'.'),
'.',
substring(substring-after($pi,'.'),1,4))"/>
and results in 3.1415.
The effect of ceiling or round
can be obtained by this technique at the cost of additional
complexity:
<xsl:variable name="whole" select="substring-before($pi,'.')"/>
<xsl:variable name="frac" select="substring-after($pi,'.')"/>
<xsl:value-of select="concat($whole,
'.',
substring($frac,1,3),
round(substring($frac,4,2) div 10))"/>
This results in 3.1416.
|