Ошибка округления 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, поскольку это общий сервер.

Solutions Collecting From Web of "Ошибка округления PHP"

Это связано с тем, что номер 1111111.505 не может быть представлен точно в нотации с плавающей запятой. Самый близкий он может получить 1111111.5049999999. Итак, что происходит, это то, что он преобразует число в вашем коде в 1111111.50499999999, а затем округляет. Который приводит к 1111111.5. Числа с плавающей запятой имеют проблемы в том, что они не могут представлять множество даже кажущихся простых десятичных чисел с полной точностью. Например. число 0,1 не может быть точно представлено с использованием двоичных чисел с плавающей запятой. Используя Python, если вы вводите 0,1, он возвращает 0.10000000000001. Плюс или минус несколько нулей. Это связано с тем, что некоторые языки, такие как .Net, предоставляют тип данных «Десятичный», который способен представлять все десятичные значения в определенном диапазоне и количестве десятичных знаков. Недостатком десятичного типа данных является то, что он медленнее, и каждый номер занимает больше места для хранения.

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

Если вам действительно нужна точность с произвольными числами с плавающей запятой, используйте 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); } 

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

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

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

25 февраля 1991 года эта проблема расчета плавающего числа в ракетном батарее MIM-104 Patriot помешала ему перехватить входящую ракету «Скад» в Дахране, Саудовская Аравия, в результате которой погибли 28 солдат из 14-го отряда офицера армии США.

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

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

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

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

Вы ожидаете, что он напечатает 0.01, верно? Но он напечатает очень странный ответ, например: 0.009999999999998

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

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

Десятичные числа не представлены в двоичном виде из-за нехватки места. Итак, вы не можете выразить 1/3 точно так же, как это 0.3333333 …, правильно? Почему мы не можем представлять 0.01, поскольку двоичный номер с плавающей запятой по той же причине. 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

Вам нужно tp обновить wetwaer, а не версию PHP.

эхо "". раунд (1,505, 2). "\ П";

Кажется, вы думаете, что попросите 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; }