Dokładność float

0

DHL w swoim WebAPI wspaniałomyślnie określa pole do wprowadzenia kwot jako float (odwzorowane jako Single w Delphi). Importując taki WSDL i tworząc kod obiektowy, a następnie wypluwając utworzony XML wartości ułamkowe, których zapis binarny nie jest precyzyjny (to znaczy nie ma rozwinięcia binarnego), zapisywane są z pełną dokładnością, a nie z wartością przekazaną. Przykładowo: 0,99 to zapis 0.990000009536743 w XML.

Czy da się to obejść inaczej, niż pobierając plik WSDL i modyfikując go, czego DHL nie chce zrobić, a jednocześnie sam się tej specyfikacji potem nie trzyma wymagając innego formatu (de facto xs:decimal) i inaczej, niż odszukując przed wysłaniem w SOAP wszystkie takie pola i zmieniając ich treść?

0

Nie znam się na SOAP i WSDL, lecz zawsze można przecież przed przetworzeniem danych po prostu obcinać te dalsze cyfry.

1

@Dzyszla:
Napisze tu bo juz się rozrosło. Na szybko w pseudo kodzie co broni ci przed zrobieniem czegos takiego ?

Wrapper {
  Wrapper(serwis_z_wsdl){
  }

getZmiennaX() {
  ustaw_precyzje_na_jaka_ccesz(serwis_z_wsdl.getZmiennaX())
}

setZmiennaX(X){
 serwis_z_wsdl.setZmiennaX(parsuj_do_wymaganej_precyzji(X))
}
0

Tak na szybko gotowiec

var
  DHL_kwota :Single;
begin
      DHL_kwota:=0.9900004564;

      label3.Caption:=(FormatFloat('0.00', DHL_kwota));
2

@mrmajer: typ Currency cechuje dokładność do czterech miejsc po przecinku, nie do dwóch.

3

Nie tak szybko z tymi zaokrągleniami.

Jeśli zmienna będzie Currency to nie ma problemu.

Jednak jeśli zmienna będzie typu innego (single, double, float32) to zostanie sformatowana przy pomocy FormatFloat w sposób niezgodny z polską rachunkowością (z amerykańską będzie zgodna).

Dla przypomnienia:

Kwoty wykazywane na fakturze zaokrągla się do pełnych groszy, przy czym końcówki poniżej 0,5 grosza pomija się, a końcówki od 0,5 grosza (czyli – uwaga! – równe 0,5 grosza albo wyższe od 0,5 grosza) - zaokrągla się do 1 grosza.

w przypadku gdy zmienna będzie innego typu niż Currency zaokrąglenie będzie nieprawidłowe (w Polsce jak już pisałem).

Oto przykłady:

var
  liczba: Single;
begin
  liczba := 1.255;
  lblWyjscie.Caption := FormatFloat('0.00', liczba); // wynik = 1,25

  liczba := 1.355;
  lblWyjscie.Caption := FormatFloat('0.00', liczba); // wynik = 1,36
end;

Jak pisałem, nie jest to zgodne z naszą rachunkowością fiskalną.

W cywilizowanym świecie, liczby parzyste zaokrągla się w dół a nieparzyste w górę. Jest to uczciwe bo średnia zbliża się do 50%. U nas państwo łupi nas na pół groszówkach w podatkach - można się już było przyzwyczaić.

Jak to rozwiązać? Można utworzyć sobie funkcję która zaokrągli w sposób zgodny z naszą rachunkowością, np. taką:

function CashRound(Cash: Extended): Currency;
begin
  if Cash > 0 then
    Result := Round((Cash + 0.00000001) * 100) / 100
  else
    Result := Round((Cash - 0.00000001) * 100) / 100;
end;

i problem rozwiązany.

var
  liczba: Single;
begin
  liczba := 1.255;
  lblWyjscie.Caption := FormatFloat('0.00', CashRound(liczba); // wynik = 1,26

  liczba := 1.355;
  lblWyjscie.Caption := FormatFloat('0.00', CashRound(liczba); // wynik = 1,36
end;
0
_13th_Dragon napisał(a):

A może po ludzku?
https://docwiki.embarcadero.com/Libraries/Sydney/en/System.Math.SetRoundMode

A porównaj z takim kodem (wiem, że wygląda dziwnie i jeszcze te stringi, ale miałem już problemy z używaniem zaokrągleń niezaleznie od ustawionego trybu)::

function Round2(const AWar: Double): Double;
var
	x: Double;
	Str: string;
begin
	if AWar < 0 then
		x := AWar * 100 - 0.5
	else
		x := AWar * 100 + 0.5;

	Str := FloatToStr(x);

	// tylko przed przecinkiem
	if Pos(FormatSettings.DecimalSeparator, Str) > 0 then
		Str := Copy(Str, 1, Pos(FormatSettings.DecimalSeparator, Str) - 1);

	Result := StrToInt64(Str) / 100;
end;```
0

Ok, przegrzebałem się przez źródła i w zasadzie problemem jest to, co niżej:

var
	s: Single;
	d: Double;
begin
	d := 0.99;
	s := 0.99;
	ShowMessage(FloatToStr(d) + #13 + FloatToStr(s));
end;

W wyniku dostajemy:

0,99
0,990000009536743

Funkcja FloatToStr nie ma przeładowanych wariacji dla innych typów danych i zawsze nakazuje obsługiwać 15 miejsc po przecinku. Niestety, cały SOAP w Delphi bazuje właśnie na tej funkcji (zamiast np. na FloatToStrF i obsłudze odrębnie typów o różnych dokładnościach).

0

@robertz68: Fajne i proste rozwiązanie, ale tak z ciekawości wszedłem na jakieś dwie losowo wybrane strony z fakturami online i przy próbie wpisania np. w polu netto kwoty 1,255zł zaokrąglało mi do 1,25zł, a przy próbie wpisania 1,355zł zaokrąglało do 1,36zł

0
mcz.rpm napisał(a):

@robertz68: Fajne i proste rozwiązanie, ale tak z ciekawości wszedłem na jakieś dwie losowo wybrane strony z fakturami online i przy próbie wpisania np. w polu netto kwoty 1,255zł zaokrąglało mi do 1,25zł, a przy próbie wpisania 1,355zł zaokrąglało do 1,36zł

no i teraz wyślij to na urządzenie fiskalne i od razu dowiesz się że pisanie programów dzisiaj jest tak proste że zajmują się tym wszyscy, nawet amatorzy.
A tak naprawdę, nie wystarczy użyć kilku frameworków, trochę kodu przekopiować z Internetu i to wszystko razem posklejać.

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