PHP ошибка округления

Я использую PHP 5.2.13 на своем сервере linux. Я получаю странную ошибку при округлении чисел. Это мой тестовый пример:

<?php
echo "        " . round(1.505, 2) . "n";
echo "       " . round(11.505, 2) . "n";
echo "      " . round(111.505, 2) . "n";
echo "     " . round(1111.505, 2) . "n";
echo "    " . round(11111.505, 2) . "n";
echo "   " . round(111111.505, 2) . "n";
echo "  " . round(1111111.505, 2) . "n";
echo " " . round(11111111.505, 2) . "n";
echo "" . round(111111111.505, 2) . "n";

вот результаты:

        1.51
       11.51
      111.51
     1111.51
    11111.51
   111111.51
  1111111.5
 11111111.51
111111111.51

кто-нибудь знает в чем причина? Я не могу обновить PHP, так как это общий сервер.

5 ответов


Это потому, что число 1111111.505 не может быть представлено точно в нотации с плавающей запятой. Ближе всего он может сделать это 1111111.5049999999. Таким образом, происходит то, что он преобразует число в вашем коде в 1111111.50499999999, а затем округляет. Что приводит к 1111111.5. Числа с плавающей запятой имеют проблемы в том, что они не могут представлять много даже, казалось бы, простых десятичных чисел с полной точностью. Например. число 0.1 не может быть точно представлено с использованием двоичных плавающих чисел. Используя Python, если вы вводите 0.1, он возвращает 0.10000000000001. Плюс-минус несколько нулей. Именно по этой причине некоторые языки, такие как .Net, предоставляют тип данных "Decimal", который может представлять все десятичные значения в определенном диапазоне и количестве десятичных знаков. Недостатком десятичного типа данных является то, что он медленнее, и каждое число занимает больше места для хранения.


если еще кто-то достигнет этой страницы с аналогичными проблемами, где плавающее вычитание чисел вызывает ошибку или странные значения. Я хочу объяснить эту проблему немного подробнее. Виновник-числа с плавающей запятой. Чтобы проиллюстрировать проблему, я объясню, почему с помощью простого примера, и вы можете предположить, почему это влияет на округление и т. д.

это напрямую не связано с PHP, и это не ошибка. Однако каждый программист должен знать об этой проблеме.

эта проблема даже забрала много жизней два десятилетия назад.

но почему это происходит?

причина в том, что значения с плавающей точкой представляют собой ограниченную точность. Итак, значение можно не имеют одного и того же строкового представления после любой обработки. Это также включает запись значения с плавающей запятой в скрипт и непосредственно печать без каких-либо математических операций.

простой пример:

$a = '36';
$b = '-35.99';
echo ($a + $b);

вы ожидаете печати 0.01, верно? Но он напечатает очень странный ответ, такой как 0.00999999999999998

Как и другие числа, числа с плавающей запятой double или float хранятся в памяти в виде строки 0 и 1. Как плавающая точка отличается от целого в том, как мы интерпретируем 0 и 1, Когда мы хотим посмотреть на них. Существует много стандартов того, как они хранятся.

числа с плавающей точкой, как правило, упакованные в компьютер данное, как бит знака, поле экспоненты и мантиссы мантисса или слева направо....

десятичные числа недостаточно хорошо представлены в двоичном формате из-за отсутствия достаточного пространства. Таким образом, uou не может выразить 1/3 точно, как это 0.3333333..., верно? Почему мы не может представлять 0.01 как двоичное число float по той же причине. 1/100-это 0.00000010100011110101110000..... с повторением 10100011110101110000.

Если 0.01 хранится в упрощенной и усеченной форме 01000111101011100001010 в двоичном формате, когда он будет переведен обратно в десятичный, он будет считываться как 0.0099999.... в зависимости от системы (64-битные компьютеры дадут вам гораздо лучшую точность, чем 32-битные). В этом случае операционная система решает, печатать ли ее так, как она видит, или как сделать его более удобочитаемым для человека. Таким образом, это зависит от машины, как они хотят ее представить. Но он может быть защищен на уровне языка с помощью различных методов.

Если вы отформатируете результат, echo number_format (0.009999999999998, 2); он будет печатать 0.01.

Это потому, что в этом случае вы указываете, как следует читать и как точность вам требуется. Ссылки на литературу: 1,2,3,4,5


Мне кажется, что вы сталкиваетесь с проблема точности, вызванная использованием поплавков. Поплавки не гарантируют полной точности, и вы можете обнаружить, что эта проблема проявляется в одной системе, но не в другой.

Если вы действительно заботитесь о точности с произвольными числами с плавающей точкой, используйте bcmath или gmp если он доступен, хотя при использовании bcmath вам нужно будет сделать функцию bcround (). Единственный, кого я нашел. фактически работы размещены в комментариях php.net bcscale страницы:

function bcround($number, $scale=0) {
        $fix = "5";
        for ($i=0;$i<$scale;$i++) $fix="0$fix";
        $number = bcadd($number, "0.$fix", $scale+1);
        return    bcdiv($number, "1.0",    $scale);
}

вам нужно обновить TP wetwaer-не версию PHP.

Эхо "" . круглый(1.505, 2) . "\n";

вы, кажется, думаете, что просите PHP вернуть округленное значение 1.505-но на самом деле получил для возврата десятичной версии округленной версии двоичной версии 1.505

попробуйте ввести цифры здесь и посмотрите на двоичное представление.


@Shabbyrobe: ваша функция неверна: попробуйте округлить это число со шкалой 2: 44069.3445

это должно быть 44069.35, но по умолчанию php round () - и ваша функция возвращает 44069.34

рабочий код членом php.net:

function mround($number, $precision=0) {

$precision = ($precision == 0 ? 1 : $precision);   
$pow = pow(10, $precision);

$ceil = ceil($number * $pow)/$pow;
$floor = floor($number * $pow)/$pow;

$pow = pow(10, $precision+1);

$diffCeil     = $pow*($ceil-$number);
$diffFloor     = $pow*($number-$floor)+($number < 0 ? -1 : 1);

if($diffCeil >= $diffFloor) return $floor;
else return $ceil;
}