• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Kurs C++ - odc. 5 (liczenie ciągu Fibonacciego)

VPS Starter Arubacloud
0 głosów
3,438 wizyt
pytanie zadane 7 lutego 2019 w C i C++ przez akinhet Nowicjusz (170 p.)

Witam, otóż kilka dni temu zacząłem naukę C++ wraz z serią video poradników pana Mirosława. Dotarłem do odcinka piątego i gdy pan Mirosław opisał na czym ma polegać ostatni przykład w tym odcinku, postanowiłem, że spróbuje sam go zrobić. Po uzupełnieniu kodu o kilka rzeczy, o których pan Mirosław mówił podczas wyjaśniania danego zadania i kilku testach zauważyłem dziwną właściwość mojego kodu. Otóż, gdy chciałem wyznaczyć 50 liczb Fibonacciego, ostatnia 50 liczba wyświetlała się jako dosyć długi zapis typu long double, jednak z minusem, a nie jak można by się spodziewać plusem. Co więcej problem znikał (50 wyświetlało się jako poprawny wynik), gdy tylko zmieniałem długość ciągu o jakąkolwiek wartość, zarówno w górę jak i w dół. Całą sytuację zbadałem nieco dogłębniej i jak się okazało problem dotyczy wszystkich liczb w których na końcu jest jedno zero, a przed nim liczba nieparzysta, choć nie wszystkich (np. 2210,  ale już 2010 wyświetla się źle). Czy może mi ktoś wytłumaczyć co jest nie tak z tym kodem? Z góry dzięki.

#include <iostream>
#include <iomanip>

using namespace std;

int liczba;

int main()
{
    cout << "Podaj do ktorej liczby chcesz wyznaczyc ciag Fibonacciego: " << endl;
    cin >> liczba;

    //Ustalanie wartości dwóch pierwszych liczb w ciągu
    long double fib[liczba-1];
    fib[0] = 1;
    fib[1] = 1;

    //Liczenie kolejnych liczb ciągu
    for(int i=2; i<liczba; i++)
    {
        fib[i] = fib[i-1] + fib[i-2];
    }

    cout << setprecision(liczba);

    //Wyświetlanie kolejnych liczb ciągu
    for(int i=0; i<liczba; i++)
    {
        cout << endl << i+1 << ". " << fib[i];
    }

    //Wyświetlanie konkretnej liczby ciągu
    //cout << liczba << ". " << fib[liczba-1];

    //Obliczanie przybliżenia złotej proporcji
    //cout << "zlota liczba " << fib[liczba-1]/fib[liczba-2];

    return 0;
}

 

2 odpowiedzi

0 głosów
odpowiedź 7 lutego 2019 przez niezalogowany
wybrane 7 lutego 2019 przez akinhet
 
Najlepsza

Co jest nie tak z tym kodem?

  1. Tworzysz tablicę o rozmiarze liczba - 1, a w pętli odwołujesz się do elementu poza tablicą(fib[i = liczba]). Więc wynik jest tak naprawdę losowy (UB). To powinno rozwiązać problem.
  2. Stosujesz VLA, co jest całkowicie niepotrzebne. Tablice o zmiennym rozmiarze tworzy się inaczej.
  3. Nie ma sensu operować na liczbach zmiennoprzecinkowych (jeżeli pierwsze wyrazy są całkowite). Typy zmiennoprzecinkowe nie są w stanie utrzymać precyzję tylko do kilku liczb. Użyj np long long int żeby wyniki były zawsze poprawne (oczywiście w obsługiwanym zakresie).
  4. Nie używaj zmiennych globalnych.
  5. Nie używaj przestrzeni nazw std::. 
  6. Nie stosuj komentarzy do opisywania trywialnych rzeczy. Kod jest na tyle czytelny, że na pierwszy rzut ok widać co się dzieje.
  7. Do nazywania zmiennych używaj języka angielskiego.

 Poprawny kod:

#include <iostream>

int main()
{
	int liczba;
	std::cout << "Podaj do ktorej liczby chcesz wyznaczyc ciag Fibonacciego: \n";
	std::cin >> liczba;

	long long int *fib = new long long int[liczba];
	fib[0] = 1;
	fib[1] = 1;

	for (int i = 2; i<liczba; i++)
	{
		fib[i] = fib[i - 1] + fib[i - 2];
	}

	for (int i = 0; i<liczba; i++)
	{
		std::cout << "\n" << i + 1 << ". " << fib[i];
	}
    delete[] fib;
}
komentarz 7 lutego 2019 przez paweljumper Obywatel (1,260 p.)
Wciąz jest ta liczba zamiast number pkt 7
komentarz 7 lutego 2019 przez akinhet Nowicjusz (170 p.)
edycja 7 lutego 2019 przez akinhet

@Hipcio,

Ok,

1. Troszkę chyba pomyliłeś, gdyż w żadnym miejscu kodu i nie równa się liczbie. Obie pętle for mają się wykonywać tylko, gdy i<liczba, więc liczba-1 powinna być ok.

2. Nie jestem pewien czy wiem o co ci chodzi, jednak jestem naprawdę początkującym (kurs zacząłem jakieś 3 - 4 dni temu) i nie widziałem innej opcji na stworzenie tabeli o zadanym przez użytkownika rozmiarze.

3. No tego akurat nie było jeszcze w kursie, więc wielkie dzięki smiley (zmieniłem teraz long double na long long int i teoretycznie działa, jednak po 92. liczbie pojawia się błąd taki sam ja w int (liczby ujemne) więc i ten system nie jest idealny).

4. Mógłbyś to trochę rozwinąć, bo chyba nie rozumiem.

5. A czy to w czymkolwiek przeszkadza? Generalnie w każdym kodzie, który pan Mirosław pokazywał pojawiała się taka linijka.

6. W pierwotnym kodzie nie było ich w ogóle, wrzuciłem je tylko po to by ktoś niedoświadczony krążący po forum mógł zrozumieć co tu się dzieje.

7. Generalnie tak robię, jednak w celu większej czytelności dla samego siebie ponazywałem je po polsku, żebym łatwiej mógł ewentualnie w przyszłości zrozumieć co tu się dzieje.

Edit: Czy mógłbyś wytłumaczyć co się dzieje w linijce 9., bo chyba jej nie rozumiem.

komentarz 7 lutego 2019 przez niezalogowany
edycja 7 lutego 2019
  1. Faktycznie troszkę się pomyliłem, ale w innej kwestii. Załóżmy, że użytkownik poda liczbę 5. Tworzysz tablicę o rozmiarze 4, a więc możesz się dostać do indeksów 0, 1, 2, 3. Natomiast pętla pozwala na dostęp indeksów: 0, 1, 2, 3, 4, bo 4 nadal jest mniejsze od liczby podanej przez użytkownika czyli 5.
  2. Niektóre kompilatory wspierają taki zapis, ale nie jest to cześć standardu C++. Nie masz gwarancji, że u kogoś innego kod będzie działać.
  3. Typ long long int może przechować dane z zakresu od -9,223,372,036,854,775,808 do 9,223,372,036,854,775,807. Można użyć jakiegoś bardziej pojemnego typu z jakiejś biblioteki od zapisu liczb. Nadal jednak long long int potrafi poprawnie obliczyć poprawnie więcej liczb. Typy zmiennoprzecinkowe tracą precyzję. Możesz sobie porównać wyniki.
  4. O tym możesz sobie doczytać. Zasięg zmiennych to bardzo podstawowe pojęcie. W profesjonalnym programowaniu praktycznie nigdy nie używa się zmiennych globalnych. Powoduje to złe praktyki i nieczytelność kodu zwłaszcza o początkujących np w dalszej nauce o funkcjach itd.
  5. O tym też możesz doczytać. Pan Mirosław nie jest zawodowym programistą C++ i w kursie popełnił dosyć sporo złych praktyk. Oczywiście możesz w małych programach używać przestrzeni nazw. Jest to dosyć trudne do zrozumienia dla początkujących. W większych projektach(na kilka plików) zrozumiesz, że tworzy to więcej problemów(kolizja nazw) i musisz więcej dopisać. W profesjonalnym programowaniu nigdy nie wczytuje się całej przestrzeni nazw w globalnym zakresie. Można wykonać pojedyncze using, albo używać przestrzeni nazw lokalnie (np. gdy są one zbyt zagnieżdżone i inaczej byłoby niewygodnie) co w sumie nadal nie jest wygodne w małych projektach. Z zalet niestosowania using namespace dla początkujących jest możliwość zapamiętania co jest w bibliotece standardowej. Zwłaszcza po kursie Mirosława Zelenta nie jest to takie wcale takie oczywiste.
  6. Mniej doświadczona osoba pewnie nie byłaby w stanie pomóc. Jeżeli zrobiłeś to dla siebie to ok (w końcu dopiero się uczysz). Zazwyczaj im mniej komentarzy tym lepiej. Jeżeli problem jest skomplikowany można w komentarzach zamieścić odnośnik do np opisu algorytmu. Pamiętaj dobry kod nie wymaga komentowania. Jeżeli postarasz się tworzyć w miarę czysty i poprawny kod w miarę dobrze ponazywany ilekroć do niego wrócisz zawsze będziesz go rozumiał(no chyba, że postanowisz porzucić programowanie). W komentarzach można zamieszczać TODO, warunki licencyjne itd. 
  7. Lepiej już teraz zacznij nazywać po angielsku. Chociażby po to żeby mieć okazją do poćwiczenia/nauki. Zazwyczaj w każdej ofercie pracy programista musi znać język angielski i to zdecydowanie więcej niż tylko "na poziomie czytania dokumentacji". To bardzo ważne.
  8. https://www.p-programowanie.pl/cpp/tablice-dynamiczne/
komentarz 7 lutego 2019 przez niezalogowany

O tym jak tworzyć tablice: https://www.p-programowanie.pl/cpp/tablice-dynamiczne/ (no tak jeszcze zapomniałem użyć delete)

komentarz 7 lutego 2019 przez akinhet Nowicjusz (170 p.)
edycja 7 lutego 2019 przez akinhet

 1. Faktycznie głupi błąd, jednak wytłumacz mi w takim razie dlaczego liczba-1 również działała cheeky.

2. Chcesz powiedzieć, że nie każdy kompilator stworzy tabelę o wielkości wyciągniętej ze zmiennej??? Trochę to dziwnie brzmi, nie sądzisz?

3. Fakt, zapomniałem o tym (ktoś w komach pod tym odcinkiem też to zauważył i pan Mirosław powiedział, że ta osoba ma rację i odesłał ją do filmu o IEEE 754).  

4. Czyli jak dobrze rozumiem, to gdybym zainicjował tą zmienną wewnątrz int main() to stała by się ona zmienną lokalną tylko dla int main(), tak? 

5. Znaczy się rozumiem, że przy bardziej złożonych programach to może być problemem, ale w przypadku takiego programiku nie sądzę, żeby to miało wpływ na cokolwiek.

6. Nie chodzi o to, żeby pomogła tylko w przypadku  w, którym spotkała by się z podobnym problemem mogła by znaleźć ten wpis i łatwiej zrozumieć mój kod. I nie chodzi mi o to, że ja przyszły super poruszający się w C++ nie ogarnę tego kodu, tylko w najbliższej przyszłości zrobię sobie np. tydzień przerwy, to będzie mi łatwiej do tego wrócić, chociaż faktycznie raczej komentarzy nie robię, jak już to po to, żeby wyłączyć na chwilę jakąś część kodu.

7. Rozumiem Ciebie jak najbardziej, ale uwierz mi, to naprawdę nie jest problem. Generalnie jak do tej pory skubałem html-a czy JS-a to praktycznie co mogłem pisałem po angielsku. Sama nauka to też nie problem, bo chodzę aktualnie do szkoły gdzie mamy 4 lekcje angielskiego tygodniowo, zaś z próbnego testu po ósmej klasie z angielskiego miałem 96%, więc no, tego wink.

8. Dzięki na pewno spojrzę zaraz smiley.

komentarz 7 lutego 2019 przez niezalogowany
  1. Nie rozumiem. Ostatnia wartość tablicy była losową wartością z pamięci.
  2. Ogółem masz standard C++, który jest wyznacznikiem wszystkiego i wszystkich. Twórcy kompilatorów w dużym, lub jeszcze większym stopniu implementują go. Stąd bierze się też kilka ciekawych zachowań takich jak unspecified behavior(UB) i implementation-defined behavior, Kompilatorów jest wiele, ale są 3 głównego nurtu (które na bieżąco implementują najnowsze standardy): GCC, Clang, MVSC. Każdy z nich ma różne dodatkowe funkcje, czasem syntaxy, czy inne funkcjonalności których nie ma w standardzie. Głównie dla celów jakiegoś wewnętrznego użycia, propozycji, dodatkowej kompatybilności z C, lub agresywnych optymalizacji. Jeżeli kod jest nieprzenośny, zależny od systemu, kompilatora to raczej się tego nie używa (chyba, że naprawdę bardzo potrzebujesz i wiesz co robisz). Przykładowo VLA nie jest obsługiwane w MVSC, czyli kod z użyciem tego kompilatora nigdy się nie skompiluje(wystąpi błąd). Podobny problem z kursu MZ to np konwersje łańcuchów na liczby (a były wygodne standardowe rozwiązania). 
  3. Załatwione
  4. Tak.  
  5. Tak, ale lepiej już od początku stosować dobre praktyki. Nadal możesz się spotkać w małych projektach z kolizją nazw. Pamiętaj, aby porzucić ten zapis przy projektach większych niż 1 plik, lub takich gdzie używasz innych bibliotek.
  6. Ok
  7. Nieźle i tak trzymaj ;) Wielu młodych nie zdaje sobie z tego sprawy jak to jest ważne dlatego wolę uświadomić. Bardzo otwiera to możliwości od nauki z obcojęzycznych źródeł po jak wspomniałem rozwój zawodowy ;)

Im szybciej zastosujesz się do tych rad tym będzie dla Ciebie lepiej. Takie dwa przykłady bardzo ważne:

  • Gdy będziesz starał się o pracę dużym plusem jest repozytorium z projektami. Oczywiście w miarę swojego rozwoju będziesz mógł wstawiać ciekawsze i lepsze problemy ALE warto udokumentować pierwsze proste projekty np kółko i krzyżyk, saper, program do szyfrowania, jakiś parser itp. Można pokazać, że systematycznie się uczyło i rozwiązywało różne problemy. Natomiast jeżeli osoba sprawdzająca repozytorium wejdzie w pierwszy lepszy plik i zobaczy pełno UB, niestandardowych zbędnych rozwiązań, brzydki kod, źle użyte using namespace, globalne zmienne, goto, funkcję exit (w złym zastosowaniu jak u MZ), złe nazewnictwo, złe formatowanie - to po prostu nie będzie miała ochoty sprawdzić innych projektów. Mimo, że będą one znacznie lepsze i ciekawsze bez takich dziwnych rzeczy. Nawet na prosty kod może być ciekawy jeżeli zadbasz o jego poprawność i "czyste" zasady co wcale nie jest takie trudne.
  • Zrobisz grę np strategię 2D z proceduralnie generowanymi mapami. Wystąpi problem, którego nie będziesz w stanie rozwiązać. Projekt będzie duży, a problem na tyle trudny że nie będzie możliwe zbudowanie odpowiedniego przykładu MCVE, bo sam możesz nie mieć pojęcia gdzie coś jest nie tak(i nikt nie będzie miał tego za złe). Ze względu na szczegółowość języka i użytych bibliotek będzie 8 osób(dla zobrazowanie) na forum, których będzie mogło Ci pomóc i na pewno jest w stanie znaleźć rozwiązanie. Pierwsze 3 nie będą miały czasu. Kolejne 2 w ogóle nie zobaczą kodu, bo mogłeś dać zbyt mało czytelny opis problemu. Kolejne osoba zobaczy, że musi pozmieniać 15 linii kodu żeby program w ogóle się u niej uruchomił i również spasuje. Następna osoba zobaczy using namespace. Jako, że w swoim projekcie używasz przestrzeni nazw std::(standard c++), sf::(sfml - grafika), thor::(rozszerzenie sfml z dodatkowymi funkcjami), noise::. Eksperci nie muszą znać wszystkich tych bibliotek żeby rozwiązać problem. Jednak wczytanie wszystkich nazw spowoduje, że nie będzie widoczne na pierwszy rzut oka co jest od czego i gdzie szukać o tym informacji, a może to jest jakiś element kodu stworzony przez osobę pytającą? 
komentarz 7 lutego 2019 przez akinhet Nowicjusz (170 p.)

1. Szczerze też nie wiem z czego to wynika. Łap kod, może u ciebie będzie coś innego (dodam, że ja korzystam z Code Blocksa z GNU GCC Compilerem).

#include <iostream>

int main()
{
    int liczba;

    std::cout << "Podaj do ktorej liczby chcesz wyznaczyc ciag Fibonacciego: " << std::endl;
    std::cin >> liczba;

    long long int fib[liczba-1];
    fib[0] = 1;
    fib[1] = 1;

    for(int i=2; i<liczba; i++)
    {
        fib[i] = fib[i-1] + fib[i-2];
    }

    for(int i=0; i<liczba; i++)
    {
        std::cout << std::endl << i+1 << ". " << fib[i];
    }

    //std::cout << std::endl << liczba << ". " << fib[liczba-1];

    //std::cout << std::endl << "zlota liczba " << fib[liczba-1]/fib[liczba-2];

    return 0;
}

2. Rozumiem to jak najbardziej, tylko nie wydało mi się to zbyt logiczne.

5. Ok, postaram się.

7. Dzięki :D Tak nieskromnie dodam, że gdy uczyłem się html-a i trochę JS-a to właśnie z takiej anglojęzycznej stronki korzystałem, chociaż html-a do końca nigdy nie ogarnąłem, gdy zaczęli dorzucać do niego jakieś zabezpieczenia związane z JS-em (a certyfikat dostałem i tak XD).

A za rady też jak najbardziej dzięki :D Przymierzam się teraz gdzie pójść po podstawówce, więc każda rada jest dla mnie super. A tak już by the way, to jak uważasz, lepsze jest technikum czy samouctwo? Bo szczerze tuż za rogiem egzamin po podstawówce, a ja nadal zbytnio nie wiem gdzie pójdę XD.

komentarz 7 lutego 2019 przez niezalogowany
1. Mój GCC faktycznie policzył ostatni wynik :D Nie zmienia to faktu, że to jest to UB i nie ma gwarancji, że zawsze tak będzie. Na przykład możesz przez przypadek spróbować przez przypadek odwołać się do pamięci innego programu co skończy się prawdopodobnie zamknięciem programu. Usuń -1 - tak jak jest w kodzie co poprawiłem.

To zależy od bardzo wielu czynników. Warto wybrać dobre technikum. W tym czasie będziesz miał czas żeby znaleźć ulubione technologie i względem nich wybierzesz dalszy rozwój. Poczytaj też inne tematy na forum o tym. Warto pomyśleć o studiach - często jest to ważny atut - chociaż jest to bardzo zależne o wybranej drogi. C++ to nie jest jedyna droga rozwoju. Pamiętaj to tym ;) Może okaże się, że będziesz wolał Javę czy Pythona. Myślę, że jak określisz swoje preferencje i będziesz miał już jakąś większą wiedzę to sam znajdziesz najlepszą dla siebie odpowiedź.
komentarz 7 lutego 2019 przez akinhet Nowicjusz (170 p.)
1. No widzisz :D Ale faktycznie, już to wyrzuciłem i przeformatowałem kod tak jak poradziłeś.

 Generalnie to w dzisiejszej perspektywie studia są priorytetem, ale jak to mówią pożyjemy, zobaczymy. Na ten moment technikum wydaje mi się najrozsądniejsze, dlatego staram się przyswoić tych kilka najpopularniejszych języków przyswoić, ale w praktyce C++ to jest drugi język za który się faktycznie poważnie zabrałem (wcześniej był html, ale go trochę porzuciłem nie znajdując w nim nic atrakcyjnego). Jeszcze po drodze była przygoda ze Small Basic-iem, ale powiedzmy sobie szczerze: to nie jest poważny język programowania. Javy, ani Pythona jeszcze nie próbowałem, chociaż słyszałem, że Java jest dość niewdzięcznym językiem i, że lepiej się zabrać za Pythona, bo podobno jest bardziej uniwersalny i przystępniejszy.
komentarz 7 lutego 2019 przez niezalogowany
Wszystko zależy od tego jak bardzo się zainteresujesz. Możesz trafić na złe studia/szkołę gdzie uczy się złych praktyk i przestarzałych technologii, albo wykładowcy nie ma zbyt dużej wiedzy. Może być pół na pół, a może być naprawdę nieźle - zdobędziesz wachlarz wiedzy i umiejętności, lub na którą mógłbyś sam nie trafić.

Co do Pythona również uważam, że jest prosty i przyjemny (chociaż słyszałem, że jest trudny w masterowaniu). Java w sumie też uważam jako fajny język do pewnych zastosowań. Wszystko zależy od preferencji ;)
komentarz 7 lutego 2019 przez akinhet Nowicjusz (170 p.)

No nie wiem, pożyjemy, zobaczymy. W każdym razie dzięki za pomoc i na razie smiley.

0 głosów
odpowiedź 7 lutego 2019 przez FuRaJ Nowicjusz (140 p.)

Cześć Akihnet!

Twój problem wynika z faktu, iż przypisałeś funkcję setprecision do zmiennej liczba zamiast wskazać maszynie liczbę cyfr co do dokładności jej obliczeń.

-> cout << setprecision(liczba);

Zamiast tego powinieneś ustawić np. 

-> cout << setprecision(50);

Dzięki temu twój program będzie wyświetlał wynik z dokładnością do pięćdziesięciu cyfr.


Odpowiedź na pytanie dlaczego w Twoim przypadku pięćdziesiąta liczba w ciągu Fibonacciego wyświetlała się niepoprawnie nasuwa się sama. 

 

Pozdrawiam cieplutko i powodzenia w nauce! ^^

 

komentarz 7 lutego 2019 przez akinhet Nowicjusz (170 p.)
edycja 7 lutego 2019 przez akinhet

Ok, w takim razie pytanie: czy setprecision nie może operować zmiennymi? Poza tym czy to przypadkiem nie oznaczało by, że każda ostatnia policzona liczba byłaby źle obliczoną? Bo generalnie jeżeli setprecision może wyciągać dane ze zmiennych to nie widzę przyczyn dlaczego tak miałoby się dziać, dlatego, że wśród liczb Fibonacciego numery porządkowe rosną znacznie szybciej niż liczba cyfr w liczbach (jest to około 1 dodatkowa cyfra co 4 - 5 kolejnych liczb).

Podobne pytania

0 głosów
1 odpowiedź 1,298 wizyt
0 głosów
2 odpowiedzi 483 wizyt
pytanie zadane 7 lipca 2015 w C i C++ przez A1ien1385 Nowicjusz (150 p.)
0 głosów
2 odpowiedzi 419 wizyt
pytanie zadane 3 maja 2015 w C i C++ przez Wiktor Stary wyjadacz (11,120 p.)

93,023 zapytań

141,986 odpowiedzi

321,288 komentarzy

62,368 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Wprowadzenie do ITsec, tom 2

Można już zamawiać tom 2 książki "Wprowadzenie do bezpieczeństwa IT" - będzie to około 650 stron wiedzy o ITsec (17 rozdziałów, 14 autorów, kolorowy druk).

Planowana premiera: 30.09.2024, zaś planowana wysyłka nastąpi w drugim tygodniu października 2024.

Warto preorderować, tym bardziej, iż mamy dla Was kod: pasja (użyjcie go w koszyku), dzięki któremu uzyskamy dodatkowe 15% zniżki! Dziękujemy zaprzyjaźnionej ekipie Sekuraka za kod dla naszej Społeczności!

...