strip_tags - Conversión de cadena de dinero PHP a error entero
strip_tags wordpress (10)
Tengo una pequeña aplicación financiera con PHP como front-end y MySQL como back-end. Tengo prejuicios antiguos y almaceno valores monetarios en MySQL como un número entero de centavos. Mis formularios HTML permiten la entrada de valores en dólares, como "156.64" y uso PHP para convertir eso a centavos y luego almaceno los centavos en la base de datos.
Tengo una función que limpia el valor en dólares del formulario y lo convierte en centavos. Saco el texto principal, desvío el texto que se arrastra, lo multiplico por 100 y lo convierto en un entero. Ese paso final es
$cents = (integer) ($dollars * 100);
Esto funciona bien para casi todo, excepto por unos pocos valores como ''156.64'' que consistentemente convierte a 15663 centavos. ¿Por qué hace esto?
Si hago esto:
$cents = (integer) ($dollars * 100 + 0.5);
entonces funciona consistentemente. ¿Por qué necesito agregar ese valor de redondeo?
Además, mis prejuicios sobre el almacenamiento de montos de dinero como enteros y no como valores de coma flotante, ¿ya no son necesarios? ¿Los cálculos de flotación modernos producirán valores monetarios muy redondeados y precisos adecuados para mantener una contabilidad 100% precisa?
Los valores de moneda / dinero nunca deben almacenarse en una base de datos (o usarse en un programa) como flotadores.
Su método entero está bien, como está usando un DECIMAL
, NUMERIC
o MONEY
donde esté disponible.
Su problema es causado por $ dólares que se tratan como un flotador y PHP no tiene un mejor tipo para tratar con dinero. Dependiendo de cuándo se asigne $ dollars, podría tratarse como una cadena o un flotante, pero sin duda se convierte en un flotante si todavía es una cadena para la operación * 100 si se ve como un flotante.
Tal vez sea mejor que analice la cadena a un valor de "dinero" entero usted mismo (utilizando una expresión regular) en lugar de confiar en las conversiones implícitas que está haciendo PHP.
Tus "prejuicios" sobre carrozas nunca serán superados, es fundamental para la forma en que funcionan. Sin entrar en demasiados detalles, almacenan un número basado en potencias de dos y dado que no todos los números decimales pueden presentarse de esta manera, no siempre funciona. Su única solución confiable es almacenar el número como una secuencia de dígitos y la ubicación del punto decimal (según el tipo DECIMAL mencionado anteriormente).
No estoy al 100% en el PHP, pero ¿es posible que la multiplicación convierta los enteros en flotantes y, por lo tanto, introduzca exactamente el problema que intentas evitar?
El código que publicó hace la multiplicación primero, forzando un cálculo de coma flotante que introduce un error, antes de convertir el valor a un número entero. En su lugar, debe evitar la aritmética de punto flotante por completo invirtiendo el orden. Primero conviértelos a valores enteros, luego realice la aritmética.
Suponiendo que el código anterior ya ha validado y formateado la entrada, intente esto:
list($bills, $pennies) = explode(''.'', $dollars);
$cents = 100 * $bills + $pennies;
Su prejuicio contra los valores de coma flotante para representar dinero está bien fundado debido al truncamiento y debido a que los valores se convierten de base-10 a base-2 y viceversa.
En lugar de usar
$ centavos = (entero) ($ dólares * 100);
Es posible que desee intentar usar:
$ centavos = bcmul ($ dólares, 100, 2);
Al convertir de float a entero, el número se redondeará hacia cero ( src ).
Lea la advertencia de precisión del punto flotante .
No tiene sentido almacenar el dinero como un entero si lo ingresas a través de una operación de coma flotante (sin juego de palabras intencionado). Si desea convertir de string
a int
y ser coherente con su "prejuicio", puede simplemente usar las funciones de cadena.
Puede usar una biblioteca de precisión arbitraria para dividir entre 10 (manejan los números internamente como cadenas), por ejemplo, bcdiv () o gmp_div_q () , pero por supuesto, también podría haberlo usado desde el principio para todas las operaciones matemáticas.
O puede usar funciones simples de cadena:
<?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);
?>
Casting no round()
como en round-to-nearest, trunca al decimal: (int)3.99
produce 3
. (int)-3.99
rinde -3
.
Dado que la aritmética de flotación a menudo induce un error (y posiblemente no en la dirección que desee), utilice round()
si desea un redondeo confiable.
Uso (int) en lugar de (entero), sugiero a otros que hagan lo mismo.
Nunca debe almacenar moneda en coma flotante, porque siempre obtiene resultados que no espera.
Echa un vistazo a php BC Maths , te permite almacenar tu moneda como una cadena y luego realizar una aritmética de muy alta precisión sobre ellos.