Program liczący, która liczba znajduje się najbliżej średniej, błąd zliczania powtórzeń pętli while

0

Piszę program w Code::Blocks 13.12, który na podstawie podanych 5 liczb wyznacza taką, która znajduje się najbliżej średniej. Za metodę przyjąłem odejmowanie w przypadku liczby większej od średniej lub dodawanie w przypadku liczby mniejszej od średniej wartości 0.001, co umieściłem w pętli while, przy okazji inkrementując wartość powtórzeń (w programie jako zmienna "kroki"). Nie rozumiem dlaczego dla liczb oddalonych tak samo od średniej przyjmuje inne wartości powtórzeń pętli np dla średniej 15, wartość kroków dla liczby 5 to 9998, dla 10 4998, dla 20 5002, a dla 25 10005. Skąd takie dziwne wartości i jak mogę naprawić ten błąd?

ps. Program nie bierze jeszcze pod uwagę występowania 2 liczb tak samo oddalonych od średniej, lecz to nie jest teraz problemem. Tak samo nie interesuje mnie póki co inne, nawet bardziej wydajne rozwiązanie problemu, a jedynie przyczyna wystąpienia takiej anomalii.

 
#include <iostream>

using namespace std;
float liczba[ 5 ], suma = 0, srednia;
int kroki[ 5 ];


int main()
{
    cout << "Program sluzy do wyznaczania liczby, ktora jest nablizsza sredniej z 5 podanych." << endl;
    cout << "Liczby, z ktorych sie ja wyznacza moga miec 2 miejsca po przecinku,"
    << endl << "za ktory sluzy kropka." << endl << endl;
    for( int i = 0; i < 5; i++ )
    {
        cout << "Podaj " << i + 1 << " liczbe : ";
        cin >> liczba[ i ];
    }
    
    for( int i = 0; i < 5; i++ )
    {
        suma += liczba[ i ];
    }
    
    cout << "Suma: " << suma << endl;
    srednia =( suma / 5 );
    cout << "Srednia: " << srednia << endl;
    
    for( int i = 0; i < 5; i++ )
    {
        kroki[ i ] = 0;
        if( liczba[ i ] < srednia )
        {
            while( liczba[ i ] < srednia )
            {
                liczba[ i ] += 0.001;
                kroki[ i ] ++;
            }
            
        }
        else if( liczba[ i ] > srednia )
        {
            while( liczba[ i ] > srednia )
            {
                liczba[ i ] -= 0.001;
                kroki[ i ] ++;
            }
            
        }
        else
        {
            kroki[ i ] = 0;
        }
        cout << "Dla liczby " << i + 1 << " liczba krokow wynosi: " << kroki[ i ] << endl;
    }
    
    return 0;
}
1

Taki urok liczb zmiennoprzecinkowych. Podstawowa rzecz - błędy operacji dodawania będą tym większe im większa jest różnica dodawanych liczb. To, że dodajesz 0.0001 nie znaczy, że dokładnie o tyle będzie więcej. I jeszcze jedno - wartość liczby traktuj przy liczbach zmiennoprzecinowych jako wartość przybliżoną, łatwiej będzie Ci się poruszać, gdyż z dużym prawdopodobieństwem będzie to wartość przybliżona liczby, którą chciałeś uzyskać, szczególnie jeśli przeprowadzisz kilka operacji arytmetycznych na tych liczbach.

2

przykład na dziesiętnych ułamkach dodawanie 1/3 gdy możemy używać tylko 3 cyfr wiodących.
0.333 + 0.333 = 0.666
0.666 + 0.333 = 0.999 a nie 1
0.999 + 0.333 = 1.33 a nie 1.332 bo mamy tylko 3 cyfry wiodące!
i tak dalej błędy się akumulują
1.33 + 0.333 = 1.66
1.66 + 0.333 = 1.99
1.99 + 0.333 = 2.32
2.32 + 0.333 = 2.65
itd

Celowo użyłem 1/3 bo 0.001 w systemie binarnym zachowuje si ę analogicznie: nie ma dokładnej skończonej reprezentacji binarnej tej liczby, tak jak 1/3 jest ułamkiem okresowym w systemie dziesiętnym.

0

Bardzo wam dziękuję, dowiedziałem czegoś nowego. :)

0

Wpierw spróbowałem double, ale ciągle nie pomogło, jednak przy tej dokładności jak użyłem long double algorytm zaczął działać tak jak planowałem. :) Wiem, że jest sposób efektywniejszy na wykonanie zadania, lecz chciałem na 2 sposoby. Jeszcze raz, dzięki.

0

Nigdy tego problemu (chodzi chyba o miedianę?) nie rozwiązywałem, ale wygląda na to że robisz coś źle.

  1. 0.001 to bardzo podejrzana tutaj wartość (tzw. code smell)
  2. nie dodaje się ułamków żeby coś uzyskać (bo raczej trudno będzie: 0.3 - 0.2 - 0.1 != 0.0).

Zajrzyj tutaj:
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php

0

oczywiście użyty przez ciebie algorytm jest beznadziejny, najlepszym sposobem naprawienia tego, jet napisanie tego normalnie używając dzielenia, zamiast liczyć ile razy trzeba dodać stałą wartość.
Myśmy się skupili na wyjaśnieniu czemu twoje cudo nie działa.

1

Faktycznie - zapomniałem o tym (choć napisałem cos co powinno podpowiedzieć to rozwiązanie). Zamiast
pętli:

            while( liczba[ i ] < srednia )
            {
                liczba[ i ] += 0.001;
                kroki[ i ] ++;
            }
 

cos takiego:

            ltmp = liczba[i];
            
            while( ltmp < srednia )
            {
                
                kroki[ i ] ++;
                ltmp = liczba[ i ] + (kroki[ i ] * 0.001);
            }
            liczba[i] = ltmp;
 

powinno poprawić dokładność. Wykorzystuje sie tu to o czym pisałem, mnożenie wprowadza najmniejszy błąd przy liczbach zmiennoprzecinkowych.

1
kroki[ i ] = (liczba[ i ]/0.001)*(1-2*FLT_EPSILON); // albo DBL_EPSILON zleżnie od typu

Gdzie to *(1-2*FLT_EPSILON) zapobiega zaokrąglaniu liczb typu 3.9999999 (błąd dokładności) do 3 gdzie tak naprawdę potrzebne jest 4.
Tu masz konwersję do int!
http://www.cplusplus.com/reference/cfloat/

1 użytkowników online, w tym zalogowanych: 0, gości: 1