Czy istnieje język programowania, w którym 1/6 zachowuje się tak samo jak 1.0 / 6.0?


11

Kiedy programowałem w C ++ kilka dni temu, popełniłem ten błąd (że mam historię!). W jednej części mojego kodu miałem 1/6 i spodziewałem się, że będzie to 0.16666666666, co nie jest prawdą. Jak wszyscy wiemy, wynikiem jest 0 - C, C ++, Java, Python - wszystkie zachowują się tak samo.

Publikuję go na mojej stronie na Facebooku, a teraz trwa debata na temat tego, czy istnieje język programowania, w którym 1/6zachowuje się tak samo jak 1.0/6.0.


5
Haskell. 1/6 = 0,16666666666666666
t123

PowerShell generuje 0.166666666666667, co mnie zaskoczyło, ponieważ 1 jest liczbą całkowitą. Założę się, że istnieją inne języki .NET, które generują oczekiwaną wartość.
JohnL,

Teoretycznie jest ich nieograniczona liczba. Oto kolejna: Rebol , a także pochodne takie jak Orca, Red i tak dalej. >> 1 / 6->== 0.166666666666667
Izkata,

Lua to robi. Ma tylko jeden typ liczby , który zwykle jest taki sam jak podwójny C.
Machado

W Clojure 1/6jest w rzeczywistości 1/6 (typ ułamkowy), co w przypadku przymusu Doublewynosi 1,66666 ...
kaoD

Odpowiedzi:


17

Czy wszyscy zapomnieli o Pascalu?

1/6daje 0.1666666...(z dowolną obsługiwaną precyzją).

1 div 6 daje 0

Jest dyskusyjne, czy reguła C jest błędem. Prawie wszystkie operatory arytmetyczne C, w których operandy są tego samego typu, dają wynik tego samego typu. Można powiedzieć coś o spójności.

Ponadto, ponieważ C jest głównie ukierunkowany na kod na poziomie systemu, większość programów C wcale nie używa zmiennoprzecinkowych. Pewnego razu przypadkowe dodanie kodu zmiennoprzecinkowego do programu, który inaczej nie potrzebowałby, może być poważnym problemem. Prawdopodobnie nadal tak jest w przypadku małych systemów wbudowanych - które znów są głównym celem dla C.

W większości programów typu C skrócenie podziału liczb całkowitych jest prawdopodobnie tym, czego chcesz.

Jeśli 1 / 6uzyskano wynik zmiennoprzecinkowy w C, to:

  • Byłaby to niespójność w języku.
  • Norma musiałaby dokonać arbitralnego wyboru, którego typu zmiennoprzecinkowego użyć w wyniku ( doublemoże to wydawać się naturalnym wyborem, ale możesz preferować dodatkową precyzję long double)
  • Język musiałby jeszcze mieć operację dzielenia liczb całkowitych; wykonanie dodawania zmiennoprzecinkowego, a następnie obcięcie prawdopodobnie nie byłoby wystarczająco dobre.

C mógł zapewnić osobne operatory dla dwóch rodzajów podziału, ale drugi punkt powyżej nadal miałby zastosowanie: który z trzech typów zmiennoprzecinkowych byłby zastosowany do wyniku? A ponieważ łatwo jest uzyskać podział zmiennoprzecinkowy, jeśli go potrzebujesz (użyj stałej zmiennoprzecinkowej dla jednego lub obu operandów lub rzutuj jeden lub oba operandy na typ zmiennoprzecinkowy), najwyraźniej nie było uważałem to za ważne.

W wersji podręcznika C z 1974 r. (Czyli 4 lata przed publikacją pierwszego wydania K&R) Ritchie nawet nie wspomina o możliwym zamieszaniu:

Binarny / operator wskazuje podział. Obowiązują te same uwagi co do mnożenia

co mówi, że jeśli oba operandy są typu intlub char, wynikiem jest typ int.

Tak, jest to źródłem zamieszania dla niektórych programistów C, szczególnie początkujących - ale C nie jest znane z tego, że jest bardzo przyjazne początkującym.


5
Pascal. Uzyskanie szczegółowych informacji, zanim C ich nie pomylił. ™
Mason Wheeler,

A potem Algol był lepszy od swoich następców (w liczbie zarówno C, jak i Pascal).
AProgrammer

Pascal - poprawność szczegółów (daj lub weź współczynnik 10)
Martin Beckett,

1
Pierwotnie napisałem 1.666666..., co jest oczywiście błędne. Moja kiepska wymówka jest taka, że ​​napisałem wydrukowany program testowy Pascala1.6666666666666667E-0001
Keith Thompson

16

W rzeczywistości to zachowanie zostało zmienione w Pythonie 3 i teraz działa tak, jak się spodziewasz ( //jest teraz używane do dzielenia liczb całkowitych).


Dzięki. Jakieś inne języki programowania masz na myśli? Może podstawowa rodzina?
Pouya,

1
@Pouya: To zachowanie jest standardowe dla rodziny Pascal. /zawsze tworzy wartość zmiennoprzecinkową, a divdo dzielenia liczb całkowitych używany jest oddzielny operator ( ).
Mason Wheeler,

13

Z popularnych języków JavaScript. 1,0 / 6,0 = 1/6 = 0,166666666666666666.

Nie uważam tego za zaskakujące. Zasadniczo, jeśli język rozróżnia typy liczb całkowitych i zmiennoprzecinkowych, podzielenie dwóch liczb całkowitych da skróconą liczbę całkowitą zamiast liczby zmiennoprzecinkowej. Jeśli nie, najprawdopodobniej domyślnie będą to operacje zmiennoprzecinkowe. Takie powinno być oczekiwane zachowanie programisty.

Pamiętaj tylko, że mogą tu być dodatkowe rzeczy, takie jak wspomniany już osobny operator podziału liczb całkowitych lub rzutowanie typu niejawnego.


6
To nie jest „ogólna zasada”. Jest to reguła C i garstka języków, które ślepo kopiowały błędy C.
Mason Wheeler,

4
@MasonWheeler: Czy FORTRAN „ślepo skopiował błąd C”? Osobiście uważam, że dobrze zaprojektowane języki powinny używać oddzielnych operatorów do podziału obciętego w porównaniu do przybliżonego podziału liczb całkowitych (a także, btw, dla wartości i równości referencyjnej), ale decyzja projektowa w czasach FORTRAN była prawdopodobnie rozsądna. To nie znaczy, że każdy język powinien robić to, co robił FORTRAN w latach pięćdziesiątych.
supercat,

3
Języki niższego poziomu muszą dokonywać tych rozróżnień. Języki wyższego poziomu / dynamiczne są często lepiej bez nich. To kompromis w projektowaniu. Doceniam posiadanie jednego dużego głupiego konstruktora / typu w JS, ale wyobrażam sobie, że czułbym, że brakuje JS, gdy próbuję napisać wysokowydajny silnik 3D bez surowszej kontroli typu. JS może mieć nakładające się narzędzie z innymi językami, ale nie sądzę, aby ktokolwiek kiedykolwiek uznał to za gotówkę do pisania wysokiej wydajności rzeczy bardziej zbliżonych do chromu.
Erik Reppen,

2
Matematyka poza programowaniem uwzględnia reguły dzielenia tylko do liczb całkowitych.
Erik Reppen,

5
@MasonWheeler: Programowanie nie jest czystą matematyką. Matematycznie 1/6 jest liczbą wymierną i nie może być dokładnie reprezentowana przez binarną liczbę zmiennoprzecinkową. Jedyną dokładną reprezentacją jest stosunek o mianowniku sześciokrotnie liczącym.
kevin cline

7

Istnieje wiele języków, w których ((1/6)*6)wyniki to 1, a nie 0. Na przykład PL / SQL, wiele dialektów BASIC, Lua.

Przypadkowo we wszystkich tych wersjach językowych 1/6 daje 0,166666667 lub 0,166666666666667 lub coś podobnego. Wybrałem wariant ((1/6) * 6) == 1, aby pozbyć się tych małych różnic.


7
To nie jest pytanie.
Roc Martí,

20
Przypadkowo we wszystkich tych wersjach językowych 1/6 daje 0,166666667 lub 0,166666666666667 lub coś podobnego. Wybrałem ((1/6)*6)==1wariant, aby pozbyć się tych drobnych różnic, ale wygląda na to, że przeceniłem umiejętności matematyczne niektórych osób.
user281377,

1
@ RocMartí tak, to naprawdę jest ...
MattDavey

1
Byłbym zaskoczony, widząc (1,0 / 6,0) * 6 równe dokładnie 1! Zaokrąglenie wyniku (1,0 / 6,0) doprowadzi do niewielkiej różnicy. (Chociaż będzie kilka języków, które domyślnie będą miały nieskończoną precyzję)
Sjoerd

1
@Sjoerd: Właściwie to nie jest zbyt zaskakujące, że jest to dokładne. Rozważ dziesiętnie scenariusz 1/11 * 11 ze wszystkimi wartościami dokładnymi do pięciu cyfr znaczących. Wartość 1/11 wynosi 9,0909 * 10 ^ -2. Pomnóż przez 11, a przed zaokrągleniem otrzymasz 99,9999 * 10 / -2. Zaokrąglij do pięciu cyfr znaczących, a wynik wyniesie 1,0000 * 10 ^ 0. Zauważ, że kluczem jest to, że mantysa 1/6 to „... 0101010101 ...”. Jeśli ostatni bit reprezentacji to „1”, pomnożenie go przez sześć i zaokrąglenie da 1. Gdyby ostatni bit był równy zero, nie zrobiłby tego.
supercat,

3

Haskell traktuje 1/6 i 1.0 / 6.0 jako identyczne 0,16666666666666666. Renderuje również 1 / 6.0 i 1.0 / 6 jako te same wartości.

Wynika to z faktu, że podstawowe typy liczbowe w języku Haskell nie są identyczne z innymi językami. Prawdziwy podział liczb całkowitych jest nieco ... skomplikowany.


2

Tak, Perl wie. Jednowarstwowy

perl -e '$x=1/6;print "$x\n";'

daje w wyniku:

0.166666666666667

Wierzę, że PHP działa w ten sam sposób.

Zredagowano, by dodać: Uważam również, że warunkiem koniecznym (ale niewystarczającym) 1/6 == 1.0/6.0jest słabe pisanie danego języka.


Dlaczego do cholery miałoby być konieczne słabe pisanie (cokolwiek to znaczy)? Po prostu zdefiniuj / do (także) oznacza podział zmiennoprzecinkowy, w przypadku gdy oba argumenty są liczbami całkowitymi.

1
@delnan - en.wikipedia.org/wiki/Weak_typing Przypuszczam, może to być możliwe, aby mieć silnie typami języka, w którym /jest przeciążony automatycznie w zależności od rodzaju argumentów, ale to wydaje się być naruszeniem zasady najmniejszych zdziwieniem do ja ...

2
Słabe / mocne pisanie jest źle zdefiniowane (jak sugeruje wiki), unikaj go i bądź konkretny. Rozumiem, że twoja definicja zakazuje domniemanej konwersji, ale nie polimorfizm ad-hoc? Jeśli tak, to rozważ Haskella, który nie ma niejawnych konwersji, ale całkiem dobrze wykonany (jak w, działa przez 99% czasu i może być zrozumiany przez śmiertelników) polimorfizm numeryczny. I dlaczego miałoby to być zadziwiające? Byłbym o wiele bardziej zdumiewający (żeby nie powiedzieć irytujący) dla mnie, gdybym musiał dodać kilka kropek do każdego pojedynczego wystąpienia dowolnego operatora, w zależności od dokładności, jakiej chciałbym.

2
@JackManey Myślę, że dla większości nowicjuszy jest znacznie bardziej zaskakujące, że 1/2 powinno być równe 0, niż gdy dzielenie dwóch liczb całkowitych powoduje podwojenie. W końcu wszystkie liczby całkowite również nie są zamknięte w podziale na matematykę. Jak podkreśla Delnan, Haskell jest przykładem silnie napisanego języka, w którym / na dwóch liczbach całkowitych nie generuje liczb całkowitych. A Python 3 jest kolejnym.
sepp2k

1
Język Haxe jest mocno (choć wywnioskowany) typowany, a jednak nie ma podziału na liczby całkowite, tylko zmiennoprzecinkowe. Więc proszę bardzo.
K.Steff,

2

W Squeak Smalltalk /na liczbach całkowitych tworzy obiekty Fraction. Więc chociaż nie jest to to samo co dzielenie zmiennoprzecinkowe, nadal (1/6)*6zwraca 1.


We wszystkich pochodnych Smalltalk-80 (to znaczy prawie we wszystkich Smalltalks). Bursztyn jest jednym ze współczesnych wyjątków (co jest zrozumiałe, ponieważ jest kompilowany do JavaScript).
herby

2

Tak, właśnie sprawdziłem TI-99 / 4A wbudowany w TI BASIC . Ponieważ traktuje wszystkie wyrażenia liczbowe jako zmiennoprzecinkowe, operacja dzielenia również jest zmiennoprzecinkowa.

 TI BASIC READY
>PRINT 1/6
  .1666666667

>


2

MATLAB. Literały liczbowe są domyślnie podwajane.

>> 1/6
ans =
    0.1667

2

Clojure domyślnie używa ułamków. To nie to samo co 1.0 / 6.0, ale możesz przekonwertować na to za pomocą floatlub doublekiedy potrzebujesz.

user=> (/ 1 6)
1/6
user=> (* (/ 1 6) 2)
1/3
user=> (pos? (/ 1 6)) ; Is 1/6 > 0?
true
user=> (float (/ 1 6))
0.16666667

1

Zaskakujące, wydaje się, że działa poprawnie w Windows PowerShell (wersja 3) .

PS C:\> 1.0 / 6.0
0.166666666666667

PS C:\> 1/6
0.166666666666667

Wydaje się również działać w Pythonie 3, jak wspomniano w sepp2k. Pozostałe dwa języki, które mam łatwo dostępne na REPL, Scala i Ruby, oba dzielą liczby całkowite i dają 0.


0

Język Rexx zawsze daje poprawną arytmetycznie odpowiedź. Na przykład: 5/2 = 2,5. Rexx to świetny język, który nie został wystarczająco wykorzystany. Teoretycznie, gdy kompilator nie może określić, czego chcesz, lepiej wykonać poprawną matematykę, jednak może to nie być wydajne. Rexx zapewnia również operator //.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.