Перейти к содержанию

Округление чисел с заданной точностью

Задача

Требуется округлить число до заданного числа десятичных знаков после запятой. Однако функции 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.