number - PHP-Precisión del número flotante
string to float php (9)
Esta pregunta ya tiene una respuesta aquí:
- ¿Las matemáticas de punto flotante están rotas? 24 respuestas
$a = ''35'';
$b = ''-34.99'';
echo ($a + $b);
Resultados en 0.009999999999998
¿Qué pasa con eso? Me preguntaba por qué mi programa seguía informando resultados extraños.
¿Por qué PHP no devuelve el esperado 0.01?
¡Porque la aritmética de coma flotante! = La aritmética de los números reales. Una ilustración de la diferencia debida a la imprecisión es, para algunos flotantes a
y b
, (a+b)-b != a
Esto se aplica a cualquier idioma que use flotadores.
Como el punto flotante son números binarios con precisión finita, hay una cantidad finita de números representables , lo que genera problemas de precisión y sorpresas como esta. Aquí hay otra lectura interesante: Lo que todo científico informático debería saber sobre la aritmética de coma flotante .
Volviendo a su problema, básicamente no hay forma de representar con precisión 34.99 o 0.01 en binario (como en decimal, 1/3 = 0.3333 ...), por lo que se usan aproximaciones en su lugar. Para solucionar el problema, puede:
Use la
round($result, 2)
en el resultado para redondearlo a 2 decimales.Usa enteros. Si esa es la divisa, digamos dólares estadounidenses, entonces almacene $ 35.00 como 3500 y $ 34.99 como 3499, luego divida el resultado entre 100.
Es una lástima que PHP no tenga un tipo de datos decimal como lo hacen other languages .
¿no sería más fácil usar number_format(0.009999999999998, 2)
o $res = $a+$b; -> number_format($res, 2);
$res = $a+$b; -> number_format($res, 2);
?
Aquí hay muchas respuestas sobre por qué los números de punto flotante funcionan de la manera en que lo hacen ...
Pero se habla poco de precisión arbitraria (Pickle lo mencionó). Si quiere (o necesita) precisión exacta, la única forma de hacerlo (para los números racionales al menos) es usar la extensión BC Math (que en realidad es solo una implementación BigNum, Arbitrary Precision ...
Para agregar dos números:
$number = ''12345678901234.1234567890'';
$number2 = ''1'';
echo bcadd($number, $number2);
dará como resultado 12345678901235.1234567890
...
Esto se llama matemática de precisión arbitraria. Básicamente, todos los números son cadenas que se analizan para cada operación y las operaciones se realizan dígito por dígito (piense en una división larga, pero hecha por la biblioteca). Entonces eso significa que es bastante lento (en comparación con los constructos matemáticos regulares). Pero es muy poderoso. Puedes multiplicar, agregar, restar, dividir, encontrar módulo y exponer cualquier número que tenga una representación de cadena exacta.
Por lo tanto, no puede hacer 1/3
con 100% de precisión, ya que tiene un decimal que se repite (y, por lo tanto, no es racional).
Pero, si quiere saber qué es 1500.0015
cuadrado:
El uso de flotadores de 32 bits (precisión doble) proporciona el resultado estimado de:
2250004.5000023
Pero bcmath da la respuesta exacta de:
2250004.50000225
Todo depende de la precisión que necesita.
Además, algo más para tener en cuenta aquí. PHP solo puede representar enteros de 32 o 64 bits (dependiendo de su instalación). Por lo tanto, si un número entero excede el tamaño del tipo int nativo (2,1 mil millones para 32 bits, 9,2 x 10 ^ 18 o 9,2 mil millones de millones para ints con signo), PHP convertirá el int en un flotante. Si bien eso no es un problema inmediato (dado que todos los enteros menores que la precisión del flotante del sistema son, por definición, directamente representables como flotantes), si intenta multiplicar dos, perderá una precisión significativa.
Por ejemplo, dado $n = ''40000000002''
:
Como número, $n
estará float(40000000002)
, lo cual está bien ya que está representado exactamente. Pero si lo float(1.60000000016E+21)
, obtenemos: float(1.60000000016E+21)
Como una cadena (usando BC math), $n
será exactamente ''40000000002''
. Y si lo string(22) "1600000000160000000004"
, obtenemos: string(22) "1600000000160000000004"
...
Entonces, si necesita la precisión con números grandes o puntos decimales racionales, es posible que desee buscar en bcmath ...
Los números flotantes, como todos los números, se deben almacenar en la memoria como una cadena de 0 y 1. Todo es bits para la computadora. La forma en que el punto flotante difiere del número entero es en la forma en que interpretamos los 0 y los 1 cuando queremos verlos.
Un bit es el "signo" (0 = positivo, 1 = negativo), 8 bits son el exponente (que van desde -128 a +127), 23 bits son el número conocido como "mantisa" (fracción). Entonces, la representación binaria de (S1) (P8) (M23) tiene el valor (-1 ^ S) M * 2 ^ P
La "mantisa" adquiere una forma especial. En la notación científica normal, mostramos el "lugar de uno" junto con la fracción. Por ejemplo:
4.39 x 10 ^ 2 = 439
En binario, el "lugar de uno" es un solo bit. Dado que ignoramos todos los 0''s más izquierdos en notación científica (ignoramos cualquier cifra insignificante) el primer bit está garantizado para ser un 1
1.101 x 2 ^ 3 = 1101 = 13
Como tenemos garantizado que el primer bit será un 1, eliminamos este bit cuando almacenamos el número para ahorrar espacio. Entonces el número anterior se almacena como solo 101 (para la mantisa). Se supone que el 1 principal
Como ejemplo, tomemos la cadena binaria
00000010010110000000000000000000
Rompiéndolo en sus componentes:
Sign Power Mantissa
0 00000100 10110000000000000000000
+ +4 1.1011
+ +4 1 + .5 + .125 + .0625
+ +4 1.6875
Aplicando nuestra fórmula simple:
(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27
En otras palabras, 0000001001010110000000000000000000 es 27 en coma flotante (según los estándares IEEE-754).
Sin embargo, para muchos números no hay una representación binaria exacta. Al igual que 1/3 = 0.333 ... repitiendo para siempre, 1/100 es 0.00000010100011110101110000 ..... con una repetición "10100011110101110000". Sin embargo, una computadora de 32 bits no puede almacenar el número completo en coma flotante. Entonces hace su mejor suposición.
0.0000001010001111010111000010100011110101110000
Sign Power Mantissa
+ -7 1.01000111101011100001010
0 -00000111 01000111101011100001010
0 11111001 01000111101011100001010
01111100101000111101011100001010
(tenga en cuenta que el negativo 7 se produce utilizando el complemento 2)
Debería quedar inmediatamente claro que 01111100101000111101011100001010 se parece en nada a 0.01
Más importante aún, sin embargo, esto contiene una versión truncada de un decimal que se repite. El decimal original contenía una repetición "10100011110101110000". Hemos simplificado esto a 01000111101011100001010
Al traducir este número de coma flotante de nuevo a decimal a través de nuestra fórmula obtenemos 0.0099999979 (tenga en cuenta que esto es para una computadora de 32 bits. Una computadora de 64 bits tendría mucha más precisión)
Porque 0.01 no se puede representar exactamente como la suma de series de fracciones binarias. Y así es como los flotadores se almacenan en la memoria.
Supongo que no es lo que quieres escuchar, pero es la respuesta a la pregunta. Para saber cómo solucionarlo, vea otras respuestas.
Utilice la función round()
PHP: http://php.net/manual/en/function.round.php
Esta respuesta resuelve un problema, pero no explica por qué. Pensé que era obvio [también estoy programando en C ++, por lo que ES OBLIGATORIO para mí;]], pero si no, digamos que PHP tiene su propia precisión de cálculo y en esa situación particular devolvió la información más completa con respecto a ese cálculo .
mi php devuelve 0.01 ...
tal vez tiene todo con la versión php, (i usando 5.2)
bcadd() puede ser útil aquí.
<?PHP
$a = ''35'';
$b = ''-34.99'';
echo $a + $b;
echo ''<br />'';
echo bcadd($a,$b,2);
?>
(producción ineficiente para mayor claridad)
La primera línea me da 0.009999999999998. Segundo me da 0.01
[Resuelto]
Cada número se guardará en la computadora por valor binario, como 0, 1. En números de precisión simple, ocupa 32 bits.
El número de punto flotante se puede presentar por: 1 bit para signo, 8 bit para exponente y 23 bit llamado mantissa (fracción).
Mira el ejemplo a continuación:
0.15625 = 0.00101 = 1.01 * 2 ^ (- 3)
signo: 0 significa número positivo, 1 significa número negativo, en este caso es 0.
exponente: 01111100 = 127 - 3 = 124.
Nota: el sesgo = 127 exponente tan sesgado = -3 + el "sesgo". En precisión simple, el sesgo es 127, por lo que en este ejemplo el exponente sesgado es 124;
En la parte de la fracción, tenemos: 1.01 media: 0 * 2 ^ -1 + 1 * 2 ^ -2
El número 1 (primera posición de 1.01) no necesita guardarse porque cuando se presenta el número flotante de esta manera, el primer número siempre será 1. Por ejemplo, convertir: 0.11 => 1.1 * 2 ^ (- 1), 0.01 => 1 * 2 ^ (- 2).
Otro ejemplo muestra siempre eliminar el primer cero: 0.1 se presentará 1 * 2 ^ (- 1). Así que el primero siempre será 1. El número presente de 1 * 2 ^ (- 1) será:
- 0: número positivo
- 127-1 = 126 = 01111110
- fracción: 00000000000000000000000 (23 número)
Finalmente: el binario sin formato es: 0 01111110 00000000000000000000000
Verifíquelo aquí: http://www.binaryconvert.com/result_float.html?decimal=048046053
Ahora si ya entiende cómo se guarda un número de coma flotante. Qué sucede si el número no se puede guardar en 32 bits (precisión simple).
Por ejemplo: en decimal. 1/3 = 0.3333333333333333333333 y porque es infinito, supongo que tenemos 5 bits para guardar los datos. Repite una vez más esto no es real. solo supongo. Entonces los datos guardados en la computadora serán:
0.33333.
Ahora cuando el número cargó la computadora, calcule nuevamente:
0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 + 3*10^-5.
Sobre esto:
$a = ''35'';
$b = ''-34.99'';
echo ($a + $b);
El resultado es 0.01 (decimal). Ahora deja mostrar este número en binario.
0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)
Compruebe aquí: http://www.binaryconvert.com/result_double.html?decimal=048046048049
Porque (01011100001010001111) se repite exactamente como 1/3. Entonces la computadora no puede guardar este número en su memoria. Debe sacrificarse. Esto no lleva precisión en la computadora.
Avanzado (Debe tener conocimiento sobre las matemáticas) Entonces, ¿por qué podemos mostrar fácilmente 0.01 en decimal pero no en binario.
Supongamos que la fracción en binario de 0.01 (decimal) es finita.
So 0.01 = 2^x + 2^y... 2^-z
0.01 * (2^(x+y+...z)) = (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists.
=> So 0.01 (decimal) must be infine in binary.