Estoy tratando de obtener la suma de 1 + 2 + ... + 1000000000
, pero yo estoy resultados divertidos en PHP y Node.js .
PHP
$sum = 0;
for($i = 0; $i <= 1000000000 ; $i++) {
$sum += $i;
}
printf("%s", number_format($sum, 0, "", "")); // 500000000067108992
Node.js
var sum = 0;
for (i = 0; i <= 1000000000; i++) {
sum += i ;
}
console.log(sum); // 500000000067109000
La respuesta correcta se puede calcular usando
1 + 2 + ... + n = n(n+1)/2
Respuesta correcta = 500000000500000000 , así que decidí probar otro idioma.
VAMOS
var sum , i int64
for i = 0 ; i <= 1000000000; i++ {
sum += i
}
fmt.Println(sum) // 500000000500000000
¡Pero funciona bien! Entonces, ¿qué hay de malo con mi código PHP y Node.js?
Quizás sea un problema de idiomas interpretados, y por eso funciona en un lenguaje compilado como Go. Si es así, ¿otros lenguajes interpretados como Python y Perl tendrían el mismo problema?
Respuestas:
Python funciona:
O:
El
int
auto de Python se convierte en Pythonlong
que admite precisión arbitraria. Producirá la respuesta correcta en plataformas de 32 o 64 bits.Esto se puede ver elevando 2 a una potencia mucho mayor que el ancho de bits de la plataforma:
Puede demostrar (con Python) que los valores erróneos que está obteniendo en PHP se deben a que PHP se está promoviendo a un valor flotante cuando los valores son mayores que 2 ** 32-1:
fuente
Su código Go usa aritmética de enteros con suficientes bits para dar una respuesta exacta. Nunca toqué PHP o Node.js, pero por los resultados sospecho que las matemáticas se hacen usando números de coma flotante y, por lo tanto, se espera que no sean exactos para números de esta magnitud.
fuente
If PHP encounters a number beyond the bounds of the integer type, it will be interpreted as a float instead. Also, an operation which results in a number beyond the bounds of the integer type will return a float instead.
- php.net/manual/en/language.types.integer.phpLa razón es que el valor de su variable entera
sum
excede el valor máximo. Y losum
que obtienes es el resultado de la aritmética de punto flotante que implica redondear. Como otras respuestas no mencionaron los límites exactos, decidí publicarlo.El valor entero máximo para PHP para:
Por lo tanto, significa que está utilizando una CPU de 32 bits o un sistema operativo de 32 bits o una versión compilada de PHP de 32 bits. Se puede encontrar usando
PHP_INT_MAX
. Sesum
calcularía correctamente si lo hace en una máquina de 64 bits.El valor entero máximo en JavaScript es 9007199254740992 . El valor integral exacto más grande con el que puede trabajar es 2 53 (tomado de esta pregunta ). El
sum
excede este límite.Si el valor entero no excede estos límites, entonces eres bueno. De lo contrario, deberá buscar bibliotecas de enteros de precisión arbitraria.
fuente
Aquí está la respuesta en C, para completar:
La clave en este caso es usar el
long long
tipo de datos C99 . Proporciona el almacenamiento primitivo más grande que C puede administrar y se ejecuta muy, muy rápido. Ellong long
tipo también funcionará en la mayoría de las máquinas de 32 o 64 bits.Hay una advertencia: los compiladores proporcionados por Microsoft explícitamente no son compatibles con el estándar C99 de 14 años, por lo que hacer que esto se ejecute en Visual Studio es una trampa.
fuente
long long
en el estándar C ++ 11. Sin embargo, ha sido una extensión MSVC ++ y g ++ durante algunos años.movabsq $500000000500000000, %rsi
gcc -O3
oclang -O3
. No sé el nombre de la optimización específica. Básicamente, el compilador nota que el resultado del bucle no depende de ningún argumento, y lo calcula en tiempo de compilación.Mi suposición es que cuando la suma excede la capacidad de un nativo
int
(2 31 -1 = 2,147,483,647), Node.js y PHP cambian a una representación de punto flotante y comienza a obtener errores de redondeo. Un lenguaje como Go probablemente tratará de mantener una forma entera (por ejemplo, enteros de 64 bits) el mayor tiempo posible (si, de hecho, no comenzó con eso). Como la respuesta cabe en un entero de 64 bits, el cálculo es exacto.fuente
El script de Perl nos da el resultado esperado:
fuente
4.99999999067109e+017
en Perl v5.16.1 MSWin32-x86.bignum
obigint
. Ambos son módulos principales, es decir, se instalan con Perl v5.8.0 o superior. Verhttp://perldoc.perl.org/bignum.html
yhttp://perldoc.perl.org/bigint.html
La respuesta a esto es "sorprendentemente" simple:
Primero, como la mayoría de ustedes saben, un número entero de 32 bits oscila entre −2,147,483,648 y 2,147,483,647 . Entonces, ¿qué sucede si PHP obtiene un resultado, que es MÁS GRANDE que esto?
Por lo general, uno esperaría un "Desbordamiento" inmediato, que hace que 2,147,483,647 + 1 se convierta en −2,147,483,648 . Sin embargo, este no es el caso. SI PHP encuentra un número mayor, devuelve FLOAT en lugar de INT.
http://php.net/manual/en/language.types.integer.php
Dicho esto, y sabiendo que la implementación de PHP FLOAT sigue el formato de doble precisión IEEE 754, significa que PHP puede manejar números de hasta 52 bits, sin perder precisión. (En un sistema de 32 bits)
Entonces, en el punto, donde su suma llega a 9,007,199,254,740,992 (que es 2 ^ 53 ) El valor Float devuelto por PHP Maths ya no será lo suficientemente preciso.
Este ejemplo muestra el punto, donde PHP está perdiendo precisión. Primero, el último bit de significación se descartará, lo que provocará que las 2 primeras expresiones den como resultado un número igual, que no lo son.
A partir de AHORA, toda la matemática saldrá mal cuando trabaje con tipos de datos predeterminados.
No lo creo. Creo que este es un problema de idiomas que no tienen seguridad de escritura. Si bien se producirá un desbordamiento de enteros como se mencionó anteriormente en todos los idiomas que usan tipos de datos fijos, los idiomas sin seguridad de tipo podrían intentar detectar esto con otros tipos de datos. Sin embargo, una vez que alcanzan su frontera "natural" (dada por el sistema), pueden devolver cualquier cosa, pero el resultado correcto.
Sin embargo, cada idioma puede tener diferentes hilos para tal escenario.
fuente
Las otras respuestas ya explicaron lo que está sucediendo aquí (precisión de coma flotante como de costumbre).
Una solución es usar un tipo entero lo suficientemente grande, o esperar que el idioma elija uno si es necesario.
La otra solución es utilizar un algoritmo de suma que conozca el problema de precisión y lo solucione. A continuación encontrará la misma sumatoria, primero con un número entero de 64 bits, luego con coma flotante de 64 bits y luego usando punto flotante nuevamente, pero con el algoritmo de suma Kahan .
Escrito en C #, pero lo mismo vale para otros idiomas también.
El resumen de Kahan da un resultado hermoso. Por supuesto, lleva mucho más tiempo calcularlo. Si desea usarlo depende a) de sus necesidades de rendimiento frente a las de precisión, yb) cómo su idioma maneja los tipos de datos de enteros frente a coma flotante.
fuente
Si tiene PHP de 32 bits, puede calcularlo con bc :
En Javascript debe usar una biblioteca de números arbitrarios, por ejemplo BigInteger :
Incluso con lenguajes como Go y Java, eventualmente tendrá que usar una biblioteca de números arbitrarios, su número resultó ser lo suficientemente pequeño para 64 bits pero demasiado alto para 32 bits.
fuente
En rubí:
Imprime
500000000500000000
, pero tarda unos buenos 4 minutos en mi Intel i7 a 2.6 GHz.Magnuss y Jaunty tienen una solución Ruby mucho más:
Para ejecutar un punto de referencia:
fuente
Uso node-bigint para cosas de enteros grandes:
https://github.com/substack/node-bigint
No es tan rápido como algo que puede usar cosas nativas de 64 bits para esta prueba exacta, pero si obtiene números mayores que 64 bits, usa libgmp debajo del capó, que es una de las bibliotecas de precisión arbitraria más rápidas que existen.
fuente
tomó años en rubí, pero da la respuesta correcta:
fuente
Para obtener el resultado correcto en php, creo que necesitaría usar los operadores matemáticos de BC: http://php.net/manual/en/ref.bc.php
Aquí está la respuesta correcta en Scala. Debe usar Longs, de lo contrario, desbordará el número:
fuente
En realidad, hay un truco genial para este problema.
Supongamos que fue 1-100 en su lugar.
1 + 2 + 3 + 4 + ... + 50 +
100 + 99 + 98 + 97 + ... + 51
= (101 + 101 + 101 + 101 + ... + 101) = 101 * 50
Fórmula:
Para N = 100: Salida = N / 2 * (N + 1)
Para N = 1e9: Salida = N / 2 * (N + 1)
Esto es mucho más rápido que recorrer todos esos datos. Su procesador se lo agradecerá. Y aquí hay una historia interesante sobre este mismo problema:
http://www.jimloy.com/algebra/gauss.htm
fuente
Esto da el resultado adecuado en PHP al forzar la conversión de enteros.
fuente
Common Lisp es uno de los lenguajes * más rápidos de interpretar y maneja enteros arbitrariamente grandes correctamente de forma predeterminada. Esto toma alrededor de 3 segundos con SBCL :
fuente
No tengo suficiente reputación para comentar la respuesta Common Lisp de @ postfuturist, pero se puede optimizar para completar en ~ 500 ms con SBCL 1.1.8 en mi máquina:
fuente
Racket v 5.3.4 (MBP; tiempo en ms):
fuente
Funciona bien en Rebol:
Esto estaba usando Rebol 3 que, a pesar de estar compilado de 32 bits, usa enteros de 64 bits (a diferencia de Rebol 2, que usaba enteros de 32 bits)
fuente
Quería ver qué pasó en CF Script
Tengo 5.00000000067E + 017
Este fue un experimento bastante bueno. Estoy bastante seguro de que podría haber codificado esto un poco mejor con más esfuerzo.
fuente
ActivePerl v5.10.1 en ventanas de 32 bits, Intel Core2duo 2.6:
resultado: 5.00000000067109e + 017 en 5 minutos.
Con el script "use bigint" funcionó durante dos horas y funcionaría más, pero lo detuve. Demasiado lento.
fuente
En aras de la integridad, en Clojure (hermoso pero no muy eficiente):
fuente
AWK:
produce el mismo resultado incorrecto que PHP:
Parece que AWK usa coma flotante cuando los números son realmente grandes, por lo que al menos la respuesta es el orden de magnitud correcto.
Pruebas de funcionamiento:
fuente
Categoría otro lenguaje interpretado:
Tcl:
Si utiliza Tcl 8.4 o anterior, depende de si se compiló con 32 o 64 bits. (8.4 es el final de la vida).
Si usa Tcl 8.5 o más reciente que tiene enteros grandes arbitrarios, mostrará el resultado correcto.
Puse la prueba dentro de un proceso para compilarlo en bytes.
fuente
Para el código PHP, la respuesta está aquí :
fuente
Puerto:
Los resultados en
500000000500000000
. (en ambas ventanas / mingw / x86 y osx / clang / x64)fuente
Erlang funciona:
fuente
Lo curioso, PHP 5.5.1 da 499999999500000000 (en ~ 30s), mientras que Dart2Js da 500000000067109000 (que es de esperar, ya que se ejecuta JS). CLI Dart da la respuesta correcta ... al instante.
fuente
Erlang también da el resultado esperado.
sum.erl:
Y usándolo:
fuente
Charla:
fuente