PHP перевод денежной строки в целочисленную ошибку - PullRequest
4 голосов
/ 11 октября 2008

У меня небольшое финансовое приложение с PHP в качестве внешнего интерфейса и MySQL в качестве внутреннего. У меня есть древние предрассудки, и я храню денежные значения в MySQL как целое число центов. Мои HTML-формы позволяют вводить долларовые значения, такие как «156.64», и я использую PHP для преобразования их в центы, а затем сохраняю центы в базе данных.

У меня есть функция, которая очищает долларовую стоимость от формы и конвертирует ее в центы. Я удаляю начальный текст, я удаляю конечный текст, умножаю на 100 и преобразую в целое число. Этот последний шаг

$cents = (integer) ($dollars * 100);

Это прекрасно работает практически для всего, кроме очень немногих значений, таких как «156.64», которые последовательно преобразуются в 15663 цента. Почему он это делает?

Если я сделаю это:

$cents = (integer) ($dollars * 100 + 0.5);

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

Кроме того, мои предрассудки относительно хранения денежных сумм в виде целых чисел, а не значений с плавающей запятой, больше не нужны? Будут ли современные вычисления с плавающей запятой производить аккуратно округленные и точные денежные значения, достаточные для ведения 100% точного учета?

Ответы [ 9 ]

4 голосов
/ 11 октября 2008

Если вам нужна точность, вы должны хранить денежные значения, используя тип данных DECIMAL в MySQL.

2 голосов
/ 13 октября 2008

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

Предполагая, что предыдущий код уже подтвердил и отформатировал ввод, попробуйте это:

list($bills, $pennies) = explode('.', $dollars);
$cents = 100 * $bills + $pennies;

Ваше предубеждение в отношении значений с плавающей запятой для представления денег обоснованно из-за усечения и из-за преобразования значений из base-10 в base-2 и обратно.

2 голосов
/ 11 октября 2008

Приведение не round(), как при округлении до ближайшего, оно усекает в десятичном виде: (int)3.99 дает 3 (int)-3.99 урожайность -3.

Поскольку арифметика с плавающей точкой часто вызывает ошибку (и, возможно, не в нужном вам направлении), используйте round(), если вы хотите надежного округления.

2 голосов
/ 11 октября 2008

Валютные / денежные значения никогда не должны храниться в базе данных (или использоваться в программе) как плавающие.

Ваш целочисленный метод в порядке, как и использование типа DECIMAL, NUMERIC или MONEY, если доступно.

Ваша проблема вызвана тем, что доллары рассматриваются как число с плавающей точкой, а у PHP нет лучшего типа для обращения с деньгами. В зависимости от того, когда назначается $ долларов, он может рассматриваться как строка или число с плавающей запятой, но, безусловно, преобразуется в число с плавающей точкой, если это все еще строка для операции * 100, если он выглядит как число с плавающей точкой.

Возможно, вам лучше разбирать строку до целочисленного значения «деньги» самостоятельно (используя регулярное выражение), а не полагаться на неявные преобразования, которые делает PHP.

2 голосов
/ 11 октября 2008

Ваши «предрассудки» по поводу поплавков никогда не будут преодолены - это фундаментально для их работы. Не вдаваясь в подробности, они хранят числа, основанные на степенях двух, и, поскольку не все десятичные числа могут быть представлены таким образом, это не всегда работает. Единственное надежное решение - сохранить число в виде последовательности цифр и местоположения десятичной точки (согласно указанному выше типу DECIMAL).

Я не на 100% на PHP, но возможно ли, что умножение преобразует целые числа в числа с плавающей точкой и, следовательно, представляет именно ту проблему, которую вы пытаетесь избежать?

1 голос
/ 05 января 2011

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

Проверьте php BC Maths , это позволит вам сохранить вашу валюту в виде строки, а затем выполнить арифметику с очень высокой точностью.

0 голосов
/ 14 января 2010

Нет смысла хранить деньги как целые, если вы вводите их с помощью операции с плавающей запятой (без каламбура). Если вы хотите конвертировать из string в int и соответствовать своему «предубеждению», вы можете просто использовать строковые функции.

Вы можете использовать библиотеку произвольной точности для деления на 10 (они обрабатывают числа внутри себя как строки), например, bcdiv () или gmp_div_q () , но, конечно, вы могли бы также использовать его с самого начала для всей математики.

Или вы можете использовать обычные строковые функции:

<?php
// Quick ugly code not fully tested
$input = '156.64';
$output = NULL;

if( preg_match('/\d+(\.\d+)?/', $input) ){
    $tmp = explode('.', $input);
    switch( count($tmp) ){
        case 1:
            $output = $tmp[0];
            break;

        case 2:
            $output = $tmp[0] . substr($tmp[1], 0, 2);
            break;

        default:
            echo "Invalid decimal\n";
    }
}else{
    echo "Invalid number\n";
}

var_dump($output);

?>
0 голосов
/ 14 января 2010

При преобразовании из числа с плавающей точкой в ​​целое число будет округлено до нуля ( src ).

Прочитайте предупреждение Точность с плавающей точкой .

0 голосов
/ 04 июля 2009

Вместо использования

$ центов = (целое число) ($ долларов * 100);

Вы можете попробовать использовать:

$ центов = bcmul (долларов, 100, 2);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...