Zaokrąglenie liczby do dwóch miejsc po przecinku, poprawnie z punktu widzenia polskiej finansowości

0

Czesc
Czy istnieje jakas funkcj, która POPRAWNIE z punktu widzenia polskiej finansowości zaokrągla liczby do dwóch miejsc po przecinku. Chodzi o funkcje która powyżej 0.005 zaokrągla w górę a poniżej w dół i jest "odporna" na dodawanie, mnożenie i inne działania. Za "odporną" uważam taką funkcję, która nie myli wyniku 1 grosz nawet po ok 20 działaniach na liczbie podstawowej lub kilku liczbach zaokrąglonych nią. Problem jest chyba dość poważny bo piszę program liczący faktury, a tam pomyłka o grosz to już katastrofa.

0

Tu sa dwie kwestie:

  1. Zaokrąglanie.
  2. Kumulowanie sie błędów.

ad1. Sprawdzałeś formaty double , extended? a może currency, bo chyba własnie do tego typu obliczeń służy. Jeśli one nie spełniają Twoich oczekiwań to napisz dokładnie co nie gra.

ad2. Zaokrąglenie powoduje zgubienie dokładności. Jesli zaokrąglasz po działaniu do dwoch miejsc po przecinku a następnie na takiej liczbie wykonujesz dalsze rachunki za każdym razem zaokrąglając to błąd będzie się kumumolwał - nie ma cudów. Wyjściem jest liczenie z większą dokładością i zaokrąglanie samego wyniku. Ponadto zawsze trzeba analizować sposób wykonywania rachunków pod względem numerycznej poprawności. Jeśli sobie z tym nie poradzisz a liczysz na pomoc to musisz napisac cos więcej, wiecej szczegółów.

Pozdrawiam

0
Ceil()
 - zaokrąglenie w kierunku rosnącym
Floor()
  • zaokrąglenie w kierunku malejącym

i dodajesz do tego

if
0

Tu sa dwie kwestie:

  1. Zaokrąglanie.
  2. Kumulowanie sie błędów.

Tak ogólnie to zapomniałem dodać jeśli to ma zanaczenie piszę go w FreePascalu
Ad.1
Liczyłem na real. Masz gdzieś opis różnych typów zmiennych, a szczególnie tych które proponujesz?

Ad.2
Błąd się kumuluje właśnie ze względu na zbyt dokładny zapis. Potrzebuję funkcji znanej mi z Excela "Licz jak wyświetlono", czyli jak mam 22,09 to liczyć muszę z tą kwotą a nie np. z 22,0923475837.

Na pl.comp.lang.pascal proponowano mi zapisywać w rekordzie oddzielnie grosze i zlote jako integer lub zamieniać liczby na string ale podczas konwersji i tak wychodzą błędy. Może ja źle te wskazówki zastosowałem.

Również pozdrawiam

Program w jego wczesniejszej wersji dla zachowania większej przejrzystości mający jednak te samą funkcję zaokrąglającą znajduje się tutaj http://195.205.11.60/~wachcio/jaro/pliki/rr.zip. Dla sprawdzenia proponuję wpisać cene netto 3447,70 i ilość 1 lub innych liczb. W rzadkich przypadkach myli się o grosz (VAT 5%).

0

Spłodziłem na szybko coś takiego, polega to na tym, ze zaokrąglana jest liczba groszy od końca, czyli jak masz kwotę np. 23.2468, to najpierw zaokrągla tą 8 na końcu, i wychodzi 23.457, potem zaokrągla nowo wytworzoną 7 na końcu, i wychodzi 23.46. Jeśli cyfra groszy jest większa od 5, to zaokrągla w górę, jeśli mniejsza lub równa, to w dół:

function TForm1.Zaokragl(Kwota : real) : real;
var
    Zlotowki, Grosze : string;
    i : integer;
    Cyfra : string;
begin
    Zlotowki := Copy(FloatToStr(Kwota), 1, Pos('.', FloatToStr(Kwota)) - 1);
    Grosze := Copy(FloatToStr(Kwota), Pos('.', FloatToStr(Kwota)) + 1, Length(FloatToStr(Kwota)));

    for i := Length(Grosze) downto 3 do begin
        //jeśli większa od 5, to podnosimy poprzednią
        if StrToInt(Grosze[i]) > 5 then begin
            Cyfra := Grosze[i - 1];
            Grosze[i - 1] := IntToStr(StrToInt(Cyfra) + 1)[1];
        end;
        Delete(Grosze, Length(Grosze), 1);
    end;

    Result := StrToFloat(Zlotowki + '.' + Grosze);
end;
0

no dobra, a jak bedzie jesli do dalszych operacji otrzymasz liczbe:

378,095123 PLN ?

Zaczniesz liczyc na liczbie 378,09 czy na 378,10 ?

Od 378,10

Z punktu widzenia polskiej nomenklatury finansowej poprawne bedzie liczenie od liczby 378,09 dlatego ze kolejne miejsca sa pomijane i nie wplywaja na zaokraglenie.

Błąd!
http://www.delphiqa.org.pl/techniques/qa067.html

Tam jest to wytłumaczone tylko nie sprawdza się w moim przypadku

0

RoundTo + F1

0

Spłodziłem na szybko coś takiego, polega to na tym, ze zaokrąglana jest liczba groszy od końca, czyli jak masz kwotę np. 23.2468, to najpierw zaokrągla tą 8 na końcu, i wychodzi 23.457, potem zaokrągla nowo wytworzoną 7 na końcu, i wychodzi 23.46. Jeśli cyfra groszy jest większa od 5, to zaokrągla w górę, jeśli mniejsza lub równa, to w dół

hehe niezle :D dziwny masz sposob myslenia ;-) 1,2345 to po zaokreglanie 1,24? wiec 1,45 to po zaokregleniu 2 :]</quote>

0

RoundTo + F1

Nawet jeżeli ta funcja działa w 100 % poprawnie to jak zapisać liczby, aby uniknąć błędu później?

Ja mam już swoją funkcję na zaokrąglanie nie wiem również czy ona jest poprawna

Function zaokraglaj(t:real): real;

Var
  u : real;
Begin

  u := t*100;
  //pomnoz liczbe oryginalna
  u := t*100-trunc(u);
  //obetnij ulamki i po odjeciu je uzyskaj
  u := u*10;
  If u>4 Then //jezeli 1/1000 jest wieksza od czterech to powieksz
        Begin
          t := t+0.001;
    End;
  zaokraglaj := t;
End;

Martwi mnie to że zwraca zmienną real. Czy to nie będzie powodowało błędu? Może użyć jakiegoś innego typu?

0

Cześć! myśle, że dobrą funkcją jesttaka oto funkcja:

FormatFloat(A:String, B:Extended): String

gdzie A jest formatem, w jakim Delphi ma zapisywać liczbę B, tzn. np.:
'0.00'. Zatem bez względu na liczbę (ilość miejsc po przecinku) Delphi zaokrągli
ją do dwóch miejsc.

0

A jeśli idzie o funkcje Ceil() oraz Floor() - zaokrąglają one tylko co do jedności,
czyli o miejscach po przecinku mowy tu nie ma.
Pozdrawiam wszystkich!!!

0

RoundTo + F1

[...]
Martwi mnie to że zwraca zmienną real. Czy to nie będzie powodowało błędu? Może użyć jakiegoś innego typu?

dziala na pewno

co do twgo typu...a znasz inne typy niz extended i jej pochodne ktore sa przecinkowe?

poczytaj w helpie to sie dowiesz co i jak... jest to proawdziwa kopalnia wiedzy.......

0
wesoledi napisał(a)

RoundTo + F1

fragment kodu z mojego programu:

"procedure TForm1.Button1Click(Sender: TObject);
var
wyp, sr, em, ch, rent : Currency;

begin
//składka emerytalna.
sr:= StrToFloat(Edit5.text);
em:=(sr*19.52)/100;
Edit1.Text:=FloatToStr(RoundTo(em,2));"

podczas kompilacji wywala mi błąd:
[Pascal Error] Unit1.pas(46): E2003 Undeclared identifier: 'RoundTo'

co robię nie tak?
dodam że samo 'Round' działa bez zarzutu.a RoundTo już nie.

0

RoundTo + F1

musisz zadeklarować odpowedni moduł w uses - Math ... ehh

0

dzięki wielkie, teraz bangla.
Co do ehhh - no cóż jestem na początku i zdaję sobie sprawę że wiele przede mną :-)

0

Nie znam Pascala/Delphi, wiec bede pisal w pseudokodzie.

Skoro w Polsce olewa sie wszelkie ulamki groszy, to zapisuj wartosc jako ilosc groszy. Wlasciwa kwota to zawsze mojaKwota/100 + odpowiednie formatowanie tekstu, zeby byly 2 miejsca po przecinku zachowane. Jezeli masz do czynienia z calkowitymi ilosciami produktow, to jest bomba (z mnozenia, dodawania i odejmowania liczb calkowitych wychodza zawsze calkowite).

Jezeli natomiast beda tez produktu z iloscia ulamkowa (np. sypkie, ciecze, itp), to liczysz nastepujaco (przyklad)

  • aktualnaKwota (za jednostke na przyklad) - 2.71 zl, u Ciebie zapisane jako 271gr
  • ilosc - 1,83 (np. litra)

wynik = 271*1.83 = 495.93
Zaograglony wynik = [wynik+0.5];
[] oznacza podloge, czyli calkowite zaokraglenie w dol. Oczywiste jest, ze dla liczb calkowitych wynik rowniez wychodzi prawidlowy.

Dalej poslugujesz sie zaograglonym wynikiem i radosnie dodajesz, odejmujesz i mnozysz reszte kwot ;)

0

nie do końca się olewa te grosze oj nie - kto ma firmę to wie że ZUS potrafi się czepić 0,5 grosza z 2001 roku :-)
trzeba wiedzieć co w górę a co w dół zaokrąglają.

0

Przyjalem zalozenia autora, jak zaokraglaja to sie nie interesowalem ;)

0

Sory za odkopywanie starego tematu, ale widzę, że jest to jedyny temat dotykający dokładnie tego problemu, z którym się zetknąłem.
A mianowicie w księgowości zaokrąglenie liczby 3,105 zł powinno być do 3,11 zł a nie do 3,10 zł.
Sprawdźcie w swoich programach wystawiających faktury jaka wam wyskakuje wartość vat po wpisaniu ceny netto 13,50 zł przy stawce vat 23%.
Funkcja RoundTo mi zaokrągla do 3,10 zł co jest błędem. Jeśli przez te lata od kiedy dyskutowano w tym temacie, wymyślono wreszcie poprawną funkcję do tego, to proszę o odpowiedź. Ja póki co wykorzystuję połączenie typu danych Currency z funkcjami FormatFloat i StrToFloat w ten oto sposób:

function obcinaj(number: extended): extended;
var
  tmp: String;
begin
  tmp := FormatFloat('0.00', number);
  obcinaj := StrToFloat(tmp);
end;

procedure ClientDataSet1BeforePost(DataSet: TDataSet);
var
  stawka_vat: integer;
  cena_netto, wartosc_netto, wartosc_vat: Currency;
begin
  //Na początku mamy tylko ilość, cenę i stawkę vat. Policzyć musimy wartość vat i wartość brutto.
  stawka_vat := ClientDataSet1['stawka_vat'];
  cena_netto := ClientDataSet1['cena_netto'];
  wartosc_netto := cena_netto * ClientDataSet1['ilosc'];
  ClientDataSet1['wartosc_netto'] := obcinaj(wartosc_netto);  //ilość może wynosić np. 1,234 kg, więc już wynik mnożenia trzeba obcinać do dwóch miejsc po przecinku
  wartosc_vat := (stawka_vat/100) * ClientDataSet1['wartosc_netto'];
  ClientDataSet1['wartosc_vat'] := obcinaj(wartosc_vat); //tu z kolei po przemnożeniu np. 0,23 * 13,50 zł też wynik trzeba obciąć do dwóch miejsc po przecinku
  ClientDataSet1['wartosc_brutto'] := ClientDataSet1['wartosc_netto'] + ClientDataSet1['wartosc_vat'];
end;

Trochę to zagmatwane, ale jeśli nie użyłbym pomocniczych zmiennych typu Currency, tylko robił działania matematyczne bezpośrednio na danych z bazy, to źle by zaokrąglał. Tak samo gdybym zamiast Currency używał np. Double, to też źle by zaokrąglał.
Ideałem byłoby dla mnie, gdybym mógł moją procedurę BeforePost napisać tak:

procedure ClientDataSet1BeforePost(DataSet: TDataSet);
begin
  ClientDataSet1['wartosc_netto'] := magiczna_funkcja_zaokraglajaca_do_2_miejsc_po_przecinku(ClientDataSet1['cena_netto'] * ClientDataSet1['ilosc']);
  ClientDataSet1['wartosc_vat'] := magiczna_funkcja_zaokraglajaca_do_2_miejsc_po_przecinku((ClientDataSet1['stawka_vat']/100) * ClientDataSet1['wartosc_netto']);
  ClientDataSet1['wartosc_brutto'] := ClientDataSet1['wartosc_netto'] + ClientDataSet1['wartosc_vat'];
end;

Pytanie tylko czy istnieje taka magiczna_funkcja_zaokraglajaca_do_2_miejsc_po_przecinku?

0

Nie ma.
RoundTo i inne pochodne to zakrąglenie Gaussowskie.
Jedyna rada to użyć Currency i napisać własną funkcję zaokrąglającą, która przyjmie jako parametr typ Currency.
Tylko w ten sposób obejdziesz niedokładności itd.

0

Sam się namęczyłem i poszukałem i znalazłem na jakimś forum prostą funkcje :

function TForm1.zaokraglij(avalue: currency; adigits: integer): currency;
var
lfactor: currency;
begin
lfactor := intpower(10, ((-1) * adigits));
if avalue < 0 then
result := trunc((avalue / lfactor) - 0.5) * lfactor
else
result := trunc((avalue / lfactor) + 0.5) * lfactor;

end;

0

canda: twoje zaokraglij() działa tak samo, jak moje obcinaj() z tą różnicą, że ty możesz jeszcze zadeklarować liczbę miejsc po przecinku, a ja mam "na sztywno" dwa miejsca (cena i tak jest zawsze dwa miejsca po przecinku). Problem w tym, że niestety zarówno w mojej, jak i twojej funkcji musimy używać zmiennych pomocniczych typu Currency bez możliwości brania danych bezpośrednio z bazy danych. Aż dziw bierze, że Borland nadal nie wymyślił takiej funkcji, która by nie zaokrąglała "Gaussowsko" (jak napisał maciejmt), tylko "księgowo" i trzeba samemu je pisać.

1
function DoDwochMiejsc(liczba:real):real;
begin
  liczba := liczba*100;
  liczba := trunc(2*liczba-trunc(liczba));
  result := liczba/100;
end;

czy jakoś tak.

0

Moja funkcja do zaokrągleń:

function RoundMT(value:double;prec:TRoundToRange;mode:TFPURoundingMode=rmNearest):double;
var
  factor,
  robin:double;
  temp:currency;
begin
  factor:=intpower(10,-prec);
  robin:=value*factor;
  if mode=rmNearest then
  begin
    temp:=abs(trunc(frac(robin)*10));
    if temp>=5 then
      mode:=rmUp
    else
      mode:=rmDown;
  end;
  temp:=int(robin);
  if mode=rmUp then temp:=temp+sign(value);
  temp:=temp/factor;
  Result:=temp;
end;

dodanie znacznika <code class="delphi"> - furious programming

0

Jestem totalnym amatorem, ale czy nie prościej (nie wiem czy optymalniej) byłoby wstawić liczbę z tyloma miejscami po przecinku do niewidzialnego Edit-u, i pętelką przepisywać do stringa liczby aż Edit.text[i-4]=',' (przepisana zostanie liczba wraz z 3 miejscami po przecinku) ? Potem pozostaje jeszcze prosta pętla do zaokrąglania, wszystko na stringach, na końcu z powrotem zamieniając to na np real.
Pozdrawiam

3

Wątku póki co nie przywracam do starej (bardzo starej) postaci, bo może coś ciekawego się tu jeszcze dopisze;

ale czy nie prościej (nie wiem czy optymalniej) byłoby wstawić liczbę z tyloma miejscami po przecinku do niewidzialnego Edit-u, i pętelką przepisywać do stringa liczby aż Edit.text[i-4]=',' (przepisana zostanie liczba wraz z 3 miejscami po przecinku) ?

A może lepiej było by utworzyć niewidzialną aplikację i do niej przekazywać łańcuch do obróbki? Albo utworzyć niewidzialny system operacyjny, i uruchomić go niewidzialnie, aby to on obrobił ten łańcuch?

Skoro już proponujesz operacje na ciągach znaków i dwustronną konwersję, to proponuj łańcuchy (np. AnsiString), a nie komponenty zawierające łańcuchy; Poza tym komponentów używa się do wyświetlania użytkownikowi potrzebnych mu informacji, a nie do niewidzialnych dla niego, wewnętrznych operacji; Myśl logicznie i bierz pod uwagę minimalizację, bo nauczysz się robić megabajtowe turbo-WTFy, a nie zgrabne i szybkie aplikacje;

i pętelką przepisywać do stringa liczby aż Edit.text[i-4]=','

Nie liczby, tylko cyfry jak już coś;

A tak już całkiem poza tym - do finansowych obliczeń stosuje się typ Currency, którego pewnie nie było w 2004 roku, kiedy ten wątek powstawał; Ale tak to jest odświeżać stare, 10-letnie kotlety...

0

Hej, jak już wspomniałem z mojej strony czysta, często prymitywna amatorszczyzna. Dlatego też wstawiłem w poprzednim poście zapytanie, a nie oznajmienie "genialnego" pomysłu :)
Pozdrawiam!

0

Modyfikując lekko kod Azariena...

function Zaograglij(liczba:real):real;
begin
  liczba := liczba*100;
  Liczba:=round(liczba);
  result := liczba/100;
end;

Czy taka forma, sprawdziłem że działa (!), nie wykona czsem mniej operacji i nie będzie ciut szybsza?

0

Oczywiście wiesz że round() zaokrągli ci 1.5 do 2 zaś 4.5 do 4 ?
Aby zrobić inaczej musisz dawać specjalne dziwaczne komendy do procesora.

0

Nie wiedziałem... w tym momencie mam do poprawy kilka programów... Człowiek uczy się na błędach. Dzięki za zwrócenie uwagi na to! Zaraz poszperam skąd to sie bierze...

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