Несподівані результати при роботі з дуже великими цілими числами на інтерпретованих мовах


192

Я намагаюся отримати суму 1 + 2 + ... + 1000000000, але я отримую смішні результати в PHP і 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

Правильну відповідь можна обчислити, використовуючи

1 + 2 + ... + n = n(n+1)/2

Правильна відповідь = 500000000500000000 , тому я вирішив спробувати іншу мову.

ПОВЕРНУТИСЯ

var sum , i int64
for i = 0 ; i <= 1000000000; i++ {
    sum += i
}
fmt.Println(sum) // 500000000500000000

Але це прекрасно працює! Отже, що не так з моїм PHP та кодом Node.js?

Можливо, це проблема інтерпретованих мов, і саме тому вона працює в мові, складеній як Go? Якщо так, чи матимуть інші інтерпретовані мови, такі як Python та Perl, таку ж проблему?


36
вам це потрібно: php.net/manual/en/book.bc.php , інакше ви будете бити головою проти IEEE 754, поки пекло не замерзне.
tereško

5
Для обробки великої кількості в PHP (тобто 64-бітної) використовуйте функції GMP, в даному випадку gmp_add ().
Джеффрі

113
Для надзвичайної ефективності петлі повинні починатися з 1 замість 0.: P
Грем Борланд

55
сума (від 1 до N) = (N / 2) * (N + 1)
Phong

5
@ Baba 0 є чудовим для вашого обчислення, тому не потрібно додаткової ітерації циклу, щоб додати 0 до 0.
Brian Warshaw

Відповіді:


155

Python працює:

>>> sum(x for x in xrange(1000000000 + 1))
500000000500000000

Або:

>>> sum(xrange(1000000000+1))
500000000500000000

intАвтомобіль Python просувається до Python, longякий підтримує довільну точність. Це дасть правильну відповідь на 32 або 64 бітних платформах.

Це можна побачити, піднявши 2 на потужність, набагато більшу, ніж бітова ширина платформи:

>>> 2**99
633825300114114700748351602688L

Ви можете продемонструвати (за допомогою Python), що помилки, які ви отримуєте в PHP, пов'язані з тим, що PHP просувається до плаваючого значення, коли значення перевищують 2 ** 32-1:

>>> int(sum(float(x) for x in xrange(1000000000+1)))
500000000067108992

Ви запускали це в 32 або 64 бітній системі?
Баба

4
Він повинен працювати незалежно (32 проти 64 біт), оскільки вбудований Python auto автоматично сприяє довільній точності, а не переповненню. Може зайняти деякий час довше тхо.
dawg

3
Python в будь-якій системі буде працювати в цьому випадку, оскільки Python автоматично переходить на довгі цілі числа, якщо це потрібно. І якщо цього недостатньо, воно також перейде на великі цілі числа.
Алок Сінгал

12
@ 0x499602D2: Це щось суворе. Сам ОП проголосував за це. Він запитав конкретно, чи це схожа проблема на Python. Відповідь, ні, це не так. Код, щоб показати, що це не так. WTH?
dawg

10
Приклад Python занадто довгий, просто використовуйте sum (xrange (int (1e9) +1)) (.... сума працює на ітерабелях)
Jason Morgan

101

Ваш код Go використовує цілу арифметику з достатньою кількістю бітів, щоб дати точну відповідь. Ніколи не торкався PHP або Node.js, але за результатами я підозрюю, що математика проводиться з використанням чисел з плаваючою комою, і, таким чином, слід очікувати, що не буде точним для чисел такої величини.


46
Так. 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.php
Нейт

3
І в NodeJS (і JavaScript взагалі) всі арифметичні операції (крім бітових операцій) ведуть себе так, ніби вони були виконані з числами з плаваючою комою. Незалежно від того, чи вони насправді є, це розрізнення, яке залежить від рішень окремих механізмів JavaScript.
Пітер Олсон

13
У специфікації JavaScript немає цілих типів. Усі числа є плаваючими точками.
toasted_flakes

8
@grasGendarme Є. Специфікація ES5 визначає різні цілочисельні перетворення та мандати, які вони можуть викликати , наприклад, порозрядним зміщенням . Тобто за лаштунками , типи цілих чисел використовуються в Javascript, але все арифметичні оператори перетворюють операнди в число з плаваючою точкою , перш ніж робити що - небудь з ними ( за винятком оптимізації компілятора).
Пітер Олсон

2
ось код, я думаю, його зіпсували, тому що я використовував float64, а не int64 .. Просто підтвердив, що він не має нічого спільного з 32 або 64 бітами
Баба,

45

Причина полягає в тому, що значення вашої цілочисельної змінної sumперевищує максимальне значення. І sumви отримуєте результат арифметики з поплавковою точкою, що включає округлення. Оскільки в інших відповідях не вказано точних меж, я вирішив опублікувати.

Максимальне ціле значення для PHP для:

  • 32-розрядна версія - 2147483647
  • 64-розрядна версія - 9223372036854775807

Отже, це означає, що ви використовуєте 32-бітний процесор або 32-бітну ОС або 32-розрядну версію PHP. Його можна знайти за допомогою PHP_INT_MAX. Розрахунок sumбуде правильно, якщо ви робите це на 64-бітній машині.

Максимальне ціле значення в JavaScript становить 9007199254740992 . Найбільша точна цілісна цінність, з якою можна працювати, - 2 53 (взяті з цього питання ). sumПеревищує цю межу.

Якщо ціле значення не перевищує цих меж, то ви добрі. Інакше вам доведеться шукати цілі цілі бібліотеки довільної точності.


28

Ось відповідь на С, для повноти:

#include <stdio.h>

int main(void)
{
    unsigned long long sum = 0, i;

    for (i = 0; i <= 1000000000; i++)    //one billion
        sum += i;

    printf("%llu\n", sum);  //500000000500000000

    return 0;
}

Ключовим у цьому випадку є використання типу даних C99 long long . Він забезпечує найбільше примітивне сховище, яке може керувати, і воно працює дуже швидко. long longТип буде також працювати на більшості 32 або 64-бітної машині.

Є одне застереження: компілятори, надані Microsoft явно, не підтримують 14-річний стандарт C99, тому запускати це в Visual Studio - це хитрощі.


3
MSVC ++ - компілятор C ++, а C ++ - long longстандарт C ++ 11. Однак, це розширення MSVC ++ та g ++ протягом кількох років.
MSalters

1
@MSalters Оскільки функція C ++, вона не допоможе дійсно нікому, хто збирає пряму програму C. Я ніколи не намагався перейти з C на C ++, тому не знаю, чи справді це рішення спрацювало.
CyberSkull

19
І прекрасно, що GCC або Clang з оптимізаціями перетворюють весь цикл наmovabsq $500000000500000000, %rsi
Tor Klingberg

3
Просто gcc -O3або clang -O3. Я не знаю назви конкретної оптимізації. В основному компілятор помічає, що результат циклу не залежить від жодного аргументу, і обчислює його під час компіляції.
Tor Klingberg

1
C99 довго має мінімальний розмір 64 біт, і наскільки я знаю, це 64 біт як на 32-бітній, так і на 64-бітній платформах. Я не бачив загальної підтримки для квадратиків чи восьмигранників.
Девін Лейн

21

Я здогадуюсь, що коли сума перевищує ємність нативного int(2 31 -1 = 2,147,483,647), Node.js та PHP переходять на подання з плаваючою комою, і ви починаєте отримувати помилки округлення. Мова на зразок Go, ймовірно, намагатиметься якомога довше дотримуватися цілої форми (наприклад, 64-бітових цілих чисел) (якщо вона справді не починалася з цього). Оскільки відповідь вписується в 64-бітове ціле число, обчислення точно.


Node.js явно не має типу int. Він працює в плавучому типі.
greyfade

@greyfade - Так, я думаю, що це стосується всіх EcmaScript-сумісних середовищ.
Тед Хопп

Хіба це не (2 ** 31 - 1)?
Мисливець на Захарі

@ZacharyHunter - Дійсно так і є. Дякуємо, що виявили цю помилку.
Тед

19

Сценарій Perl дає нам очікуваний результат:

use warnings;
use strict;

my $sum = 0;
for(my $i = 0; $i <= 1_000_000_000; $i++) {
    $sum += $i;
}
print $sum, "\n";  #<-- prints: 500000000500000000

3
Ви запускали це в 32 або 64 бітній системі?
Баба

2
він був виконаний на 64-
бітній

3
4.99999999067109e+017на Perl v5.16.1 MSWin32-x86.
Qtax

7
Якщо вам справді потрібні великі цифри, використовуйте bignumабо bigint. Обидва є основними модулями, тобто вони встановлюються з Perl v5.8.0 або вище. Дивіться http://perldoc.perl.org/bignum.htmlіhttp://perldoc.perl.org/bigint.html
shawnhcorey

Я отримав 500000000500000000, працюючи на 32-розрядному ПК PPC під керуванням Perl 5.12.4.
CyberSkull

17

Відповідь на це "напрочуд" проста:

По- перше - як і більшість з вас , можливо , знаєте - це 32-розрядний ціле число знаходиться в діапазоні від -2147483648 до 2147483647 . Отже, що станеться, якщо PHP отримає результат, тобто ВЕЛИЧЕ, ніж це?

Зазвичай можна очікувати негайного "переповнення", в результаті чого 2,147,483,647 + 1 перетвориться на −2,147,483,648 . Однак це НЕ так. Якщо PHP зустрічається з більшою кількістю, він повертає FLOAT замість INT.

Якщо PHP зустрічає число, що виходить за межі цілого типу, воно буде інтерпретуватися як плаваючий. Також операція, в результаті якої число виходить за межі цілого типу, замість цього поверне плавець.

http://php.net/manual/en/language.types.integer.php

Це говорить, і знаючи, що реалізація PHP FLOAT слідує за формою подвійної точності IEEE 754, означає, що PHP може працювати з числами до 52 біт, не втрачаючи точності. (Для 32-бітної системи)

Отже, у Точці, де ваша сума сягає 9,007,199,254,740,992 (що становить 2 ^ 53 ) Значення Float, повернене математикою PHP, вже не буде достатньо точним.

E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000000\"); echo number_format($x,0);"

9,007,199,254,740,992

E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000001\"); echo number_format($x,0);"

9,007,199,254,740,992

E:\PHP>php -r "$x=bindec(\"100000000000000000000000000000000000000000000000000010\"); echo number_format($x,0);"

9,007,199,254,740,994

Цей приклад показує точку, де PHP втрачає точність. По-перше, останній bititat біт буде скинутий, що призведе до того, що перші 2 вирази призводять до рівного числа - чого вони не є.

Із ЗАРАЗУ вся математика піде не так під час роботи з типами даних за замовчуванням.

• Чи така ж проблема в інших інтерпретованих мовах, таких як Python чи Perl?

Я не думаю, що так. Я думаю, що це проблема мов, які не мають безпеки типу. Хоча переповнення цілого числа, як згадувалося вище, відбуватиметься на будь-якій мові, що використовує фіксовані типи даних, мови без безпеки типу можуть спробувати вирішити це за допомогою інших типів даних. Однак, як тільки вони потраплять на "природний" (заданий Системою) кордон - вони можуть повернути що завгодно, але правильний результат.

Однак кожна мова може мати різні потоки для такого сценарію.


15

Інші відповіді вже пояснювали, що відбувається тут (точність з плаваючою точкою, як зазвичай).

Одним із варіантів є використання цілого типу достатньо великого розміру, або сподіватися, що мова буде обрана, якщо це потрібно.

Іншим рішенням є використання алгоритму підсумовування, який знає про проблему точності та працює навколо неї. Нижче ви знайдете те саме підсумовування спочатку з 64-бітовим цілим числом, потім з 64-бітовою плаваючою точкою, а потім знову з використанням плаваючої точки, але з алгоритмом підсумовування Кахана .

Написано на C #, але те ж саме стосується й інших мов.

long sum1 = 0;
for (int i = 0; i <= 1000000000; i++)
{
    sum1 += i ;
}
Console.WriteLine(sum1.ToString("N0"));
// 500.000.000.500.000.000

double sum2 = 0;
for (int i = 0; i <= 1000000000; i++)
{
    sum2 += i ;
}
Console.WriteLine(sum2.ToString("N0"));
// 500.000.000.067.109.000

double sum3 = 0;
double error = 0;
for (int i = 0; i <= 1000000000; i++)
{
    double corrected = i - error;
    double temp = sum3 + corrected;
    error = (temp - sum3) - corrected;
    sum3 = temp;
}
Console.WriteLine(sum3.ToString("N0"));
//500.000.000.500.000.000

Підсумок Кахана дає прекрасний результат. Звичайно, для обчислення потрібно багато часу. Від того, чи хочете ви його використовувати, залежить: а) від вашої продуктивності та точності, а також від того, як ваша мова обробляє цілі чи типи даних з плаваючою комою.


@Baba Це те саме, що і з Node.js / JavaScript в ОП. Щодо того, чому 500000000067109000 проти 500000000067108992… поняття не маю.
linac

Можливо, Бабу заінтригує використання крапок для тисячі роздільників: англійська зазвичай очікує коми. Ви також можете використовувати підкреслення як більш нейтральне значення.
didierc

14

Якщо у вас є 32-розрядний PHP, ви можете обчислити його за допомогою bc :

<?php

$value = 1000000000;
echo bcdiv( bcmul( $value, $value + 1 ), 2 );
//500000000500000000

У Javascript ви повинні використовувати бібліотеку довільних чисел, наприклад BigInteger :

var value = new BigInteger(1000000000);
console.log( value.multiply(value.add(1)).divide(2).toString());
//500000000500000000

Навіть з такими мовами, як Go та Java, вам з часом доведеться використовувати довільну бібліотеку номерів, ваш номер просто малий для 64-розрядних, але занадто високий для 32-розрядних.


12

У Рубі:

sum = 0
1.upto(1000000000).each{|i|
  sum += i
}
puts sum

Друкує 500000000500000000, але на моєму 2,6 ГГц Intel i7 займає 4 хвилини.


У Magnuss і Jaunty є набагато більше рішення Ruby:

1.upto(1000000000).inject(:+)

Для запуску еталону:

$ time ruby -e "puts 1.upto(1000000000).inject(:+)"
ruby -e "1.upto(1000000000).inject(:+)"  128.75s user 0.07s system 99% cpu 2:08.84 total

10
1.upto (1000000000) .inject (: +)
Магнус

@Magnuss: Це те, що я вважав, що спробував спочатку, але це спричинило масовий витік пам'яті. Здається, ваша робота ...
cgenco

11

Я використовую node-bigint для великих цілих матеріалів:
https://github.com/substack/node-bigint

var bigint = require('bigint');
var sum = bigint(0);
for(var i = 0; i <= 1000000000; i++) { 
  sum = sum.add(i); 
}
console.log(sum);

Це не так швидко, як щось, що може використовувати рідний 64-бітний матеріал для цього точного тесту, але якщо ви потрапляєте в більшу кількість, ніж 64-розрядні, він використовує libgmp під кришкою, яка є однією з швидших довільних бібліотек точності там.


4

зайняла віки в рубіні, але дає правильну відповідь:

(1..1000000000).reduce(:+)
 => 500000000500000000 

4

Щоб отримати правильний результат у php, я думаю, вам знадобиться використовувати математичні оператори BC: http://php.net/manual/en/ref.bc.php

Ось правильна відповідь у Scala. Ви повинні використовувати Longs, інакше ви переповнюєте число:

println((1L to 1000000000L).reduce(_ + _)) // prints 500000000500000000

3

Насправді є крута хитрість до цієї проблеми.

Припустимо, натомість це було 1-100.

1 + 2 + 3 + 4 + ... + 50 +

100 + 99 + 98 + 97 + ... + 51

= (101 + 101 + 101 + 101 + ... + 101) = 101 * 50

Формула:

Для N = 100: вихід = N / 2 * (N + 1)

Для N = 1e9: вихід = N / 2 * (N + 1)

Це набагато швидше, ніж переглядати всі ці дані. Ваш процесор буде вдячний вам за це. І ось цікава історія щодо цієї проблеми:

http://www.jimloy.com/algebra/gauss.htm


11
Як ви думаєте, можливо, можна пройти через кожен міст через Прегель у Калінінграді, не перетинаючи жодного мосту двічі? Багато людей намагалися і не вдалися, але ще ніхто не встановив, що це неможливо. Це здається проблемою, яку ви б вирішили вирішити однозначно.
jwg

3

Це дає належний результат у PHP, вимушуючи цілий набір.

$sum = (int) $sum + $i;

3

Загальний Lisp - одна з * найшвидше інтерпретованих * мов і обробляє довільно великі цілі числа за замовчуванням. На SBCL це займає приблизно 3 секунди :

* (time (let ((sum 0)) (loop :for x :from 1 :to 1000000000 :do (incf sum x)) sum))

Evaluation took:
  3.068 seconds of real time
  3.064000 seconds of total run time (3.044000 user, 0.020000 system)
  99.87% CPU
  8,572,036,182 processor cycles
  0 bytes consed

500000000500000000
  • Під інтерпретацією, я маю на увазі, я запустив цей код з REPL, SBCL, можливо, зробив якийсь JITing внутрішньо, щоб змусити його працювати швидко, але динамічний досвід роботи коду негайно той же.

Можна спростити як (час (цикл для х від 1 до 1000000000 сума х)). Я отримав ~ 5x швидкість, додавши декларацію: (час (локально (оголосити (оптимізувати (швидкість 3) (безпека 0))))) (цикл для i-type fixnum від 1 до 1000000000 суми i типу fixnum)))
huaiyuan

Це помилково. Не дозволяйте вам осліпнути іншими мовами! Правильний спосіб записати це в Lisp: (розминути суму-від-1-до-n (n) (/ (* n (1+ n)) 2)) (час (сума-від-1-до-n 1000000000)) зайняло 14 мікросекунд (0,000014 секунд). За цей період і з 4 доступними ядрами процесора в режимі користувача було проведено 0 мікросекунд (0,000000 секунд). У системному режимі було проведено 0 мікросекунд (0,000000 секунд) -> 500000000500000000
інформатимаго

@informatimago: Це не помилково. Я копіював імперативний стиль циклу питання, і як багато хто вказував, саме питання зазначає, що існує простіший спосіб обчислити. Chillax.
постфутурист

3

У мене недостатньо репутації, щоб коментувати загальну відповідь Lisp @ @ postfuturist, але її можна оптимізувати, щоб виконати через ~ 500 мс з SBCL 1.1.8 на моїй машині:

CL-USER> (compile nil '(lambda () 
                        (declare (optimize (speed 3) (space 0) (safety 0) (debug 0) (compilation-speed 0))) 
                        (let ((sum 0))
                          (declare (type fixnum sum))
                          (loop for i from 1 to 1000000000 do (incf sum i))
                          sum)))
#<FUNCTION (LAMBDA ()) {1004B93CCB}>
NIL
NIL
CL-USER> (time (funcall *))
Evaluation took:
  0.531 seconds of real time
  0.531250 seconds of total run time (0.531250 user, 0.000000 system)
  100.00% CPU
  1,912,655,483 processor cycles
  0 bytes consed

500000000500000000

3

Ракетка v 5.3.4 (MBP; час у мс):

> (time (for/sum ([x (in-range 1000000001)]) x))
cpu time: 2943 real time: 2954 gc time: 0
500000000500000000

1
Видалену мою відповідь опублікували через 6 хвилин після вас, як тільки я помітив вашу. :)
Грег Хендершот

3

Чудово працює в Реболі:

>> sum: 0
== 0

>> repeat i 1000000000 [sum: sum + i]
== 500000000500000000

>> type? sum
== integer!

Для цього використовується Rebol 3, який, незважаючи на 32-бітну компіляцію, використовує 64-бітні цілі числа (на відміну від Rebol 2, який використовував 32 бітні цілі числа)


3

Я хотів побачити, що сталося в CF Script

<cfscript>
ttl = 0;

for (i=0;i LTE 1000000000 ;i=i+1) {
    ttl += i;
}
writeDump(ttl);
abort;
</cfscript>

Я отримав 5,00000000067E + 017

Це був досить акуратний експеримент. Я досить впевнений, що міг би зробити це трохи краще, доклавши більше зусиль.


3

ActivePerl v5.10.1 для 32-бітових windows, intel core2duo 2.6:

$sum = 0;
for ($i = 0; $i <= 1000000000 ; $i++) {
  $sum += $i;
}
print $sum."\n";

результат: 5,00000000067109e + 017 за 5 хвилин.

З "use bigint" сценарій працював дві години, і працював би більше, але я зупинив це. Занадто повільно.


Чи може хто-небудь підтвердити, що це дійсно скільки часу додає, що займає багато бінтінгів?
jwg

3

Для повноти в Clojure (красиво, але не дуже ефективно):

(reduce + (take 1000000000 (iterate inc 1))) ; => 500000000500000000

1
Єдиний крихітний корисний елемент, який мають відповіді $ MY_FAVOURITE_LANGUAGE - це якщо вони дають результат ...
jwg

@jwg Так, вибачте, що пропустив кінець рядка - оновлена ​​відповідь.
Blacksad

3

AWK:

BEGIN { s = 0; for (i = 1; i <= 1000000000; i++) s += i; print s }

дає такий же неправильний результат, як і PHP:

500000000067108992

Здається, AWK використовує плаваючу крапку, коли числа дійсно великі, тому принаймні відповідь - це правильний порядок розмірів.

Випробування:

$ awk 'BEGIN { s = 0; for (i = 1; i <= 100000000; i++) s += i; print s }'
5000000050000000
$ awk 'BEGIN { s = 0; for (i = 1; i <= 1000000000; i++) s += i; print s }'
500000000067108992

2

Категорія інша інтерпретована мова:

Tcl:

Якщо ви використовуєте Tcl 8.4 або старішої версії, це залежить від того, чи він був складений з 32 або 64 біт. (8.4 - це кінець життя).

Якщо ви використовуєте Tcl 8.5 або новіший з довільними великими цілими числами, він відобразить правильний результат.

proc test limit {
    for {set i 0} {$i < $limit} {incr i} {
        incr result $i
    }
    return $result
}
test 1000000000 

Я поставив тест всередині протоколу, щоб отримати його у байті.


2

Щодо коду PHP, відповідь тут :

Розмір цілого числа залежить від платформи, хоча максимальне значення близько двох мільярдів - це звичайне значення (це 32 біти підписано). 64-бітні платформи зазвичай мають максимальне значення приблизно 9E18. PHP не підтримує непідписані цілі числа. Розмір цілого числа може бути визначений за допомогою постійної PHP_INT_SIZE, а максимальне значення, використовуючи постійну PHP_INT_MAX з PHP 4.4.0 та PHP 5.0.5.


2

Гавань:

proc Main()

   local sum := 0, i

   for i := 0 to 1000000000
      sum += i
   next

   ? sum

   return

Результати в 500000000500000000. (для обох windows / mingw / x86 та osx / clang / x64)


2

Ерланг працює:

from_sum(From,Max) ->
    from_sum(From,Max,Max).
from_sum(From,Max,Sum) when From =:= Max ->
    Sum;
from_sum(From,Max,Sum) when From =/= Max -> 
    from_sum(From+1,Max,Sum+From).

Результати: 41> марно: from_sum (1,1000000000). 500000000500000000


2

Забавна річ, PHP 5.5.1 дає 499999999500000000 (за ~ 30 s), тоді як Dart2Js дає 500000000067109000 (чого можна очікувати, оскільки це JS, який виконується). CLI Dart дає правильну відповідь ... миттєво.


2

Ерланг також дає очікуваний результат.

sum.erl:

-module(sum).
-export([iter_sum/2]).

iter_sum(Begin, End) -> iter_sum(Begin,End,0).
iter_sum(Current, End, Sum) when Current > End -> Sum;
iter_sum(Current, End, Sum) -> iter_sum(Current+1,End,Sum+Current).

І використовуючи це:

1> c(sum).
{ok,sum}
2> sum:iter_sum(1,1000000000).
500000000500000000

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.