Округление чисел с заданной точностью¶
Задача¶
Требуется округлить число до заданного числа десятичных знаков после запятой. Однако функции XSLT round
, ceiling
и floor
всегда возвращают целое число.
Решение¶
XSLT 1.0¶
Умножим на 10 в степени n, где n - требуемое число десятичных знаков, затем округлим и разделим на тот же коэффициент. Предположим, что $pi = 3.1415926535897932
. Тогда вычисление выражения
<xsl:value-of select="round($pi * 10000) div 10000" />
дает 3.1416
. Аналогично:
<xsl:value-of select="ceiling($pi * 10000) div 10000" />
дает 3.1416
, а:
<xsl:value-of select="floor($pi * 10000) div 10000" />
дает 3.1415
.
Округлить до заданного числа знаков можно также с помощью функции format-number()
:
<xsl:value-of select="format-number($pi, '#.####')" />
Получится 3.1416
. Это решение будет работать, даже если в целой части всего одна значащая цифра, поскольку функция format-number никогда не интерпретирует спецификацию формата как указание удалять значащие цифры из целой части:
<xsl:value-of select="format-number($pi * 100, '#.####')" />
В результате получаем 314.1593
.
С помощью функции format-number можно получить эффект отбрасывания вместо округления, если задать в форматной строке на одну цифру больше, чем необходимо, а затем отбросить последний символ:
<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"/>
Результат будет равен 3.1415
.
XSLT 2.0¶
В большинстве приложений задачу может решить появившаяся в XPath 2.0 функция round-half-to-even()
. Half to even (округление половины до четного) означает, что в случае, когда округляемая величина оказывается точно посередине между большим и меньшим значением, округление производится в том направлении, где получится четный результат. Так, round-half-to-even(1.115,2) eq 1.12, и round-half-to-even(1.125,2) eq 1.12!
В первом случае мы округляем с избытком, поскольку 2 – четное число, а во втором – с недостатком, так как 3 – число нечетное. Теоретически такой метод обосновывается тем, что если имеется много чисел, то округление с избытком должно происходить примерно так же часто, как с недостатком, поэтому ошибки округления будут взаимно уничтожаться. Вы, конечно, догадались, что второй аргумент функции round-half-to-even() – число десятичных знаков после округления.
Если в вашем приложении требуется, чтобы число, оканчивающееся на 5, всегда округлялось с избытком, то можете воспользоваться техникой, описанной в рецепте для XSLT 1.0.
Обсуждение¶
Метод «умножение, округление, деление» хорошо работает, если все промежуточные результаты не выходят за пределы представимости числа с плавающей точкой в формате IEEE. Если вы попробуете сохранить слишком много знаков после запятой, то результат будет искажен вследствие правил работы с числами с плавающей точкой. Например, попытка получить 16 десятичных знаков числа π даст всего 15:
<xsl:value-of
select="round($pi * 10000000000000000) div 10000000000000000"
/>
В результате получим 3.141592653589793
, а не 3.1415926535897932.
Альтернативное решение – обращаться с числом, как со строкой, а затем обрезать лишние цифры:
<xsl:value-of
select="concat(substring-before($pi,'.'),'.',substring(substring-after($pi,'.'),1,4))"
/>
дает 3.1415
.
Этот прием позволяет добиться того же эффекта, что ceiling
или round
, но ценой дополнительной сложности:
<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))"/>
В результате получаем 3.1416
.