Porównywanie wartości Double / Currency - Dlaczego tak się dzieje?

0

Pod kodem wstawiłem opis problemu.

function SumujRozliczenia: Currency;    //Currency, Duble  nie ma znaczenia
begin
   Result := 7916.2;
   edtWartosc.FloatValue := Result; 
end;

procedure Porownanie;
var
   E1,E2 : Extanded;
   C1,C2 : Currency;
   D1,D2 : Double;
begin
   C2 := edtWartosc.FloatValue;   //FloatValue jest typu Double
   D2 := edtWartosc.FloatValue;  

   if SumujRozliczenia > edtWartosc.FloatValue then
     sleep(1); //To się wykona

   if SumujRozliczenia > Currency(edtWartosc.FloatValue) then
     sleep(1); //To się wykona

   if SumujRozliczenia > C2  then
     sleep(1); //To się nie wykona   
end;

To że double pokazuje np 7916.2 Wartość tak naprawdę może wynosić 7916.199 ... niby Currency ma to rozwiązać jeżeli dodajemy np wartości z dokładnością do 4 miejsc po przecinku.
I myślałem że ten przypadek ** if SumujRozliczenia > Currency(edtWartosc.FloatValue) then** zadziała ale nie delphi wykrywa różne wartości.

Chciałem Was zapytać jak sobie radzić z takim problemem czy za każdym razem gdy porównuje wartości typu Double mam używać funkcji z modułu Math SameValue , CompareValue np.

   if CompareValue(SumujRozliczenia,edtWartosc.FloatValue,0.001)  = GreaterThanValue then
     sleep(1); //To się nie wykona
2

Dobrą praktyką jest nie stosowanie porównań wartości zmiennoprzecinkowych.
http://docwiki.embarcadero.com/RADStudio/Sydney/en/About_Floating-Point_Arithmetic

0

@PrzemysławWiśniewski: Czyli najbezpieczniej w przypadku dodawania, mnożenia itp wartości pieniężnych (do 4 miejsc po przecinku) jest ich pomnożenie przez 10000 i odrzucenie wartości za przecinkiem. I wtedy możemy bezpiecznie porównywać ponieważ będzie to zwykła liczba całkowita.

5

@Rafał D: ależ Currency to właśnie jest liczba całkowita (wewnętrznie reprezentowana jako Int64), więc przesuwanie przecinka jest bez sensu — nie dość, że niczego tym nie zyskujesz, to w dodatku zmniejszasz zakres możliwych liczb do przechowania (o rząd wielkości).

Wszelkie operacje dotyczące pieniążków powinny być wykonywane wyłącznie na typie Currency (bez żadnych zaokrągleń i testowaniem delty), bo do tego właśnie został zaprojektowany. Twoim problemem nie jest przecinek, a niepoprawna konwersja Double na Currency i mieszanie tych typów w obliczeniach.

0

Ja stosuję extended co do zasady nie bawię się w Double czy Currency:

SumujRozliczenia

procedure TForm1.Button1Click(Sender: TObject);
begin
 Caption := FormatFloat('0.00', 7916.2);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
a, b : extended;
begin
 a := 13.53;
 b := 86.47;
 Caption := FormatFloat('0.00', a + b);
end;

procedure TForm1.Button3Click(Sender: TObject);
var
a, b : extended;
begin
 a := 1000000000000000.00;
 b := 123.11;
 Caption := FormatFloat('0.00', a + b);
end;
5
Mariusz Bruniewski napisał(a):

Ja stosuję extended co do zasady nie bawię się w Double czy Currency: […]

Mam głęboką nadzieję, że nie pracujesz nad jakąkolwiek, nawet najmniejszą aplikacją przeprowadzającą obliczenia finansowe. W przeciwnym razie Twoi klienci będą mieli kumulujące się dzień w dzień problemy z powodu Twojej ignorancji.

0

Wartość pieniądza po przecinku ma dwa miejsca z dokładnością po przecinku. Zatem w czym problem?

procedure TForm1.Button1Click(Sender: TObject);
var
  zl : Currency;
begin
  zl := 1234.56;
  ThousandSeparator := '.';
  ShowMessage('Wartość w zł = '+ CurrToStrF(zl, ffCurrency, 2));
end;
5
Mariusz Bruniewski napisał(a):

Wartość pieniądza po przecinku ma dwa miejsca z dokładnością po przecinku. Zatem w czym problem?

NIE, dokładność Currency to CZTERY miejsca po przecinku!

Zamiast wypisywać bzdury (w dodatku w cudzym wątku), lepiej idź czytać dokumentację, skoro przez 16 lat obcowania z Delphi nie masz pojęcia na temat takich podstaw. Przestań z siebie robić głąba i rozsiewać nieprawdziwe informacje. :|

uses
  SysUtils;
var
  Value: Double;
begin
  Value := 1234.56;
  Write(FloatToStrF(Value, ffFixed, 0, 2));
end.

Double też ma dokładność do dwóch miejsc po przecinku — tyle samo jest to warte co Twój kod. Wpisujesz do zmiennej wartość wypełniającą połowę precyzji typu, a do tego jeszcze przy konwersji na ciąg obcinasz precyzję do dwóch miejsc i dumnie oznajmiasz, że Currency obsługuje dwa miejsca. A wystarczy wpisać wartość z dłuższą częścią dziesiętną i jak byk na ekranie dostaniesz precyzję dziesięciotysięczną:

uses
  SysUtils;
var
  Value: Currency;
begin
  Value := 1234.5678;
  Write(CurrToStr(Value));
end.

Output:

1234.5678

Albo w ten sposób:

uses
  SysUtils;
var
  Value: Currency;
begin
  Value := 1234.5678;

  GetFormatSettings();
  Write(CurrToStrF(Value, ffCurrency, 4, DefaultFormatSettings));
end.

I na wyjściu dostaniesz to:

$1,234.5678

Tzn. ja dostanę, bo mam angielską lokalizację. Obcięło mi precyzję do dwóch miejsc? Nie. A jak spróbuję wpisać do zmiennej Currency liczbę ze znacznie wyższą precyzją niż ten typ danych obsługuje, czyli np. taką:

uses
  SysUtils;
var
  Value: Currency;
begin
  Value := Double(1234.5678912345);

  GetFormatSettings();
  Write(CurrToStrF(Value, ffCurrency, 4, DefaultFormatSettings));
end.

to dostanę wynik zaokrąglony do czterech miejsc, bo owe zaokrąglenie jest wykonywane automatycznie, tak aby ”hajs się zgadzał” — tak to działa, bo tak to ma działać. I na wyjściu dostanę $1,234.5679 — nie dlatego, że mi funkcja konwertująca liczbę na ciąg znaków obcięła precyzję, a dlatego, że Currency obsługuje precyzję do czterech miejsc po przecinku.

Jak nadal nie masz pewności to sobie ją przekonwertuj na wyższą precyzję i dostaniesz dopełnienie zerami:

screenshot-20210523182535.png

0

@furious programming wpierw napisałeś szczątkowo. Później zacząłeś pisać kod i udowadniać :-)

1

Nie napisałem szczątkowo — pokazałem w jaki sposób sam się oszukujesz. Też wpisałem liczbę z dwumiejscową precyzją do zmiennej Double i przy konwersji obciąłem precyzję do dwóch miejsc. I tak samo ten kod jest nic nie warty jak Twój. A to że rozwinąłem odpowiedź do kilku przykładów niczego nie zmienia, bo pierwsza część została niezmieniona i nadal dokładnie pokazuje gdzie leży problem związany z precyzją.

0
procedure TForm1.Button1Click(Sender: TObject);
var
a, b : Currency;
begin
 a := 1234.5678;
 b := 1234.5678;
 Caption := FormatFloat('0.0000', a + b);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
a, b : Double;
begin
 a := 1234.5678;
 b := 1234.5678;
 Caption := FormatFloat('0.0000', a + b);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
a, b : Double;
begin
 a := 1234.5678;
 b := 1234.5678;
 Caption := FormatFloat('0.00000000', a + b); // sam ustalam ilość liczb po przecinku
end;
0

@Mariusz Bruniewski:
żeby zobaczyć różnicę pomiędzy Currency i Extended
odpal sobie prosty kod testowy

procedure TForm1.Button1Click(Sender: TObject);
var
  zlExt: extended;
  zlCurr: currency;
  i: integer;
begin
  for i := 0 to 10000 do
  begin
    zlExt := i / 10000;
    zlCurr := i / 10000;
    if zlExt <> zlCurr then
      self.Memo1.Lines.Add(inttostr(i));
  end;
end;
0

Mój znajomy, były oficer policji, mówi że w tej firmie od dawna pozytywny wzorzec to
"odporny na wiedzę i nie do zaj..nia"

@grzegorz_so obawiam się, że wysiłek edukacyjny w tym przypadku będzie zmarnowany

W programowaniu taką rolę pełnią tzw "praktycy", co to rzetelne książki są dla nich zbyt skomplikowane

0

Ja uwzględniałem grosze i zł. Stad 2 miejsca po przecinku.

procedure TForm1.Button1Click(Sender: TObject);
var
  zl : Currency;
begin
  zl := 1234.5678912345; // zapis furious programming
  ThousandSeparator := '.';
  ShowMessage('Wartość w zł = '+ CurrToStrF(zl, ffCurrency, 2));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  zl : Currency;
begin
  zl := 12345678912345.78; // nie zaokrągli po 8 
  ThousandSeparator := '.';
  ShowMessage('Wartość w zł = '+ CurrToStrF(zl, ffCurrency, 2));
end;

0

@Mariusz Bruniewski:
grosze nie mają nic do znaczenia
przykład z groszami

procedure TForm1.Button1Click(Sender: TObject);
var
  zlExt: extended;
  zlCurr: currency;
  i: integer;
begin
  for i := 0 to 100 do
  begin
    zlExt := i / 100;
    zlCurr := i / 100;
    if zlExt <> zlCurr then
      self.Memo1.Lines.Add(inttostr(i));
  end;
end;

Z przykładu widać że nawet wartości wyrażone w pełnych groszach i zapisane do zmiennej typy Extended nie dają w wartości tej zmiennej dokładnej reprezentacji. Poczytaj jak wygląda typ Extended, może zrozumiesz dlaczego np. nie można w w tym typie zapisać dokładnej wartości "0.09" a tylko jej przybliżenie

1
Mariusz Bruniewski napisał(a):

Ja uwzględniałem grosze i zł. Stad 2 miejsca po przecinku.

Czy Ty mieszkasz w afrykańskim buszu i nigdy nie słyszałeś o takich rzeczach jak kantory, banki, giełdy i inne instytucje wykorzystujące czterocyfrową część dziesiętną do operacji finansowych? Hmm? Nadal nie ogarniasz, że typy zmiennoprzecinkowe przechowują przybliżenie liczby, w odróżnieniu od typu stałoprzecinkowego, jakim jest Currency? Jak długo jeszcze zamierzasz iść w zaparte i się błaźnić?

Po raz kolejny wnioskuję o zaprzestanie wypisywania bzdur i propagowania zupełnie błędnej, wyssanej z palca wiedzy.

0

@grzegorz_so

procedure TForm1.Button1Click(Sender: TObject);
var
a, b : extended;
begin
 a := 100; // zł, którymi dysponuje
 b := 0.09; // kupuję coś za 9 groszy
 Caption := 'Reszta : ' + FormatFloat('0.00', a - b);
end;
3

@Mariusz Bruniewski:
podajesz tylko przykłady potwierdzające Twoją tezę, a wystarczy jeden aby ją obalić

procedure TForm1.Button2Click(Sender: TObject);
var
  a, b: extended;
begin
  a := 1;  // 1 zł, którymi dysponuje
  b := 0.09; // kupuję gazetę za 9 gr, powinno mi zostać 91 gr
  if a-b=0.91 then
    showmessage('Masz rację')
  else
    showmessage('Nie masz racji')
end;
0

@grzegorz_so

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: extended;
  s : string;
begin
  a := 1;  // 1 zł, którymi dysponuje
  b := 0.09; // kupuję gazetę za 9 gr, powinno mi zostać 91 gr

  Caption := 'Reszta : ' + FormatFloat('0.00', a - b);

  s := FormatFloat('0.00', a - b);

  s := StringReplace(s, ',', '.', [rfReplaceAll]);

   if s = '0.91' then
   showmessage('Masz rację')
  else
    showmessage('Nie masz racji');
end;
3

@Mariusz Bruniewski:
porównuj wartości zmiennych a nie ich tekstową prezentację

i wyjaśnij dlaczego kod działa tak jak widać, czyli "Nie masz racji"

procedure TForm1.Button2Click(Sender: TObject);
var
  a, b: extended;
begin
  a := 1;  // 1 zł, którymi dysponuje
  b := 0.09; // kupuję gazetę za 9 gr, powinno mi zostać 91 gr
  if a-b=0.91 then
    showmessage('Masz rację')
  else
    showmessage('Nie masz racji')
end

@Mariusz Bruniewski:
próbujesz dyskutować o czymś o czym nie masz pojęcia. Podałem kilka przykładów kładące twoje "tezy" na łopatki

0

@grzegorz_so a może mam rację?

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c, d : extended;
begin
  a := 1;    // 1 zł, którymi dysponuje
  b := 0.09; // kupuję gazetę za 9 gr, powinno mi zostać 91 gr
  d := 0.91; // reszta

  c := StrToFloat(FormatFloat('0.00', a - b));

  if c = d then
    showmessage('Masz rację')
  else
    showmessage('Nie masz racji')
end;
5

Nie wierzę, że ktoś kłóci się o używania zmiennych zmienno przeciekowych do reprezentacji monetarnych... Liczby IEEE745 mają skończoną precyzję, oraz precyzja ta jest zmienna, natomiast przez samą konstrukcje nigdy nie pokazuje dokładnej liczby dziesiętnej. To jest stosowane do innych obliczeń, NIGDY DO CEN. Już nie jeden program zaczął dodawać lub zjadać pieniądze, z tego powodu. Rzutowanie do stringi jest debilne. Sorry, ale inaczej tego nie można nazwać, a kolejne operacje mogę spowodować, że niedokładność wpłynie tez na reprezentacje stringową... Źródła wiedzy do uzupełnienia - https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency https://pl.wikipedia.org/wiki/Liczba_zmiennoprzecinkowa. Tak btw to jest 1-2 rok informatyki... Sugeruję Mariuszka zbanować, bo wprowadza ludzi w błąd i jego działania są wysoce szkodliwe. Do momentu, jak się upierał, że złe działanie jego perpetum mobile wynika z niedoskonałości Delphi, a nie jego niewiedzy, to było szkodliwe dla niego. Teraz wprowadza ludzi w kardynalne błędy.

4

@Mariusz Bruniewski:
a co to za koszmarek

StrToFloat(FormatFloat('0.00', a - b));

porównuj zmienne !!!
myślę że dalsza dyskusja nie ma sensu.. nic nie rozumiesz

0

Jako że piszę programy współpracujące z drukarkami fiskalnymi od dawna to mam małą zagadkę:
sprzedajecie 0,5 kg ziemniaków po 2,13 zł za kilogram.
Ile wysyłacie na drukarkę?

0

Na moje to przesyłasz ilość w typie numerycznym 0,5 oraz cenę w typie KWOTA - z 2 miejscami po przecinku czyli 0213.

2

bardziej chodziło mi o to że jest pewien problem z zaokrągleniami w delphi (i nie tylko) jeśli chodzi o waluty.
Ilość i wartość jaką wybrałem daje sumę 1,065 i standardowo język programowania zaokrągla to do 1,06 a niestety powinno być 1,07.

Obsługując drukarki fiskalne musisz przesłać zawsze kilka wartości: nazwę, stawkę vat, ilość, cenę i koniecznie wyliczoną wartość która musi być taka sama jak wartość wyliczona przez drukarki fiskalne (taka dodatkowa kontrola poza sumą kontrolną).
Ile to ja widziałem "błędów interfejsu" przy obsłudze drukarek fiskalnych z programów nawet dużych graczy na rynku.
Najgorsza sytuacja jest przy rabatach procentowych, szczególnie gdy wychodzą wartości z dużą ilością miejsc po przecinku. Padają na tym nawet najwięksi.

Ale piszę to tak tylko dla rozluźnienia tematu :).

1

Wartość podajesz przy zamknięciu paragonu. Przy pozycjach dajesz same kwoty i ilości. Co do zaokrąglania - to tak, powinno iść w górę, zgodnie z zasadami matematyki.

0

wiesz co, może oprogramowujesz inne drukarki ale w posnecie (i novitusie) jest tak:

ESC P Pi $1 <nazwa> CR <ilość> CR <ptu> / CENA / BRUTTO / <check> ESC \

BRUTTO to wartość

0

No nie bardzo bo w Posnecie linia wygląda tak:

[STX]trline[TAB]naZiemniaki[TAB]vt2[TAB]pr213[TAB]il0,5[TAB]jmKg[TAB]#CRC16[ETX]
0

a widzisz, używasz protokołu Posnet, ja nadal cały czas używam protokołu Thermal. W protokole posnet jest też pole wartość "wa" ale rzeczywiście nie jest wymagane.

Ale to dużo nie zmienia, i tak na koniec paragonu trzeba wysłać sumę wszystkich pozycji i jeśli wcześniej źle to obliczyliśmy (bo wydawało się nam że Delphi zrobi to za nas) to błąd gwarantowany.

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