Dlaczego Math.pow () (czasami) nie jest równe ** w JavaScript?

118

Właśnie odkryłem funkcję ECMAScript 7 a**bjako alternatywę dla Math.pow(a,b)( Dokumentacja MDN ) i natknąłem się na dyskusję w tym poście , w której najwyraźniej zachowują się inaczej. Przetestowałem to w Chrome 55 i mogę potwierdzić, że wyniki się różnią.

Math.pow(99,99) zwroty 3.697296376497263e+197

natomiast

99**99 zwroty 3.697296376497268e+197

Tak więc rejestrowanie różnicy Math.pow(99,99) - 99**99skutkuje -5.311379928167671e+182.

Do tej pory można by powiedzieć, że to po prostu kolejna implementacja, ale opakowanie jej w funkcję zachowuje się znowu inaczej:

function diff(x) {
  return Math.pow(x,x) - x**x;
}

wywołanie diff(99)zwrotów 0.

Dlaczego tak się dzieje?

Jak zauważył xszaboj , można to zawęzić do tego problemu:

var x = 99;
x**x - 99**99; // Returns -5.311379928167671e+182
Thomas Altmann
źródło
7
Wygląda na to, że ktoś przepisał algorytm, którego użył, i znaleziono błąd zmiennoprzecinkowy . Liczby są trudne ...
krillgar
4
@krillgar brzmi rozsądnie, ale dlaczego w takim razie ten sam błąd nie występuje w funkcji?
Thomas Altmann,
3
@AndersonPimentel Łącze MDN wskazuje na tabelę zgodności .
Álvaro González
7
różnica jest między tymi dwoma: var x = 99; x * * x; i 99 * * 99. Lub function diff (x) {return 99 * * 99 - (x * * x); }; diff (99). Przepraszamy za odstępy, komentarz filtruje dwie gwiazdki :(
xszaboj
1
@xszaboj umieścić kod na backticks `likethis`aby czytelny, a także uniknąć pogrubienie / kursywa problemu
phuclv

Odpowiedzi:

126

99**99jest oceniana w czasie kompilacji ( „ciągłego zwijania”) oraz kompilator powrutyna jest różna od jednego wykonawczego . Podczas obliczania **w czasie wykonywania wyniki są identyczne z Math.pow- nic dziwnego, ponieważ **jest faktycznie kompilowany do Math.powwywołania:

console.log(99**99);           // 3.697296376497268e+197
a = 99, b = 99;
console.log(a**b);             // 3.697296376497263e+197
console.log(Math.pow(99, 99)); // 3.697296376497263e+197

Tak właściwie

99 99 = 369729637649726772657187905628805440595668764281741102430259972423552570455277523421410650010128232727940978889548326540119429996769494359451621570193644014418079915603086599847998499996693099849984999966930998499849999667999966930998499849999667999966930998499849999667999966799849984999966799667998499996679984999966799849999667999966799849999667996679984998499996679966799849999667998499996679999666

więc pierwszy wynik jest lepszym przybliżeniem, nadal taka rozbieżność między wyrażeniami stałymi i dynamicznymi nie powinna mieć miejsca.

To zachowanie wygląda jak błąd w wersji 8. Zostało to zgłoszone i miejmy nadzieję, że wkrótce zostanie naprawione.

Georg
źródło
19
Czyli w zasadzie JS próbuje wcześniej poprawić wydajność za pomocą obliczeń 99**99? Czy można to uznać za błąd, ponieważ Math.powtworzy takie same dane wyjściowe dla liczb i zmiennych, a **nie?
Thomas Altmann
3
@ThomasAltmann: Math.rowjest zawsze uruchomiona , składanie const można wykonać tylko dla operatorów. Tak, to zdecydowanie błąd.
georg
11
Po wyglądzie rzeczy przez OP został zarejestrowany błąd .
James Thorpe
5
Używam MS krawędzi i wszystkie 3 wyniki są takie same: 3.697296376497263e+197, 3.697296376497263e+197, i 3.697296376497263e+197odpowiednio. To zdecydowanie błąd Chrome.
Nolonar,
4
@ThomasAltmann Jeśli stałe zwijanie daje gorszą wartość niż implikowana w czasie wykonywania, to jest to błąd. Jeśli daje lepszą wartość niż środowisko wykonawcze, może, ale nie musi, zostać uznane za błąd. W tym przypadku jest lepiej - prawidłowa wartość to "... 26772 ...", stałe składanie daje "... 268" (poprawnie zaokrąglone), a środowisko wykonawcze daje "... 263" (od 4+ jednostki na ostatnim miejscu).
hobbs