Dokładność float

Dzyszla
  • Rejestracja:około 3 lata
  • Ostatnio:około 5 godzin
  • Postów:108
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ść?

edytowany 2x, ostatnio: Riddle
Zobacz pozostałe 15 komentarzy
Dzyszla
Dokładnie! Tylko po stronie Delphi ja tego nie umiem wymusić z definicji (tak właśnie brzmi pytanie), a po stronie DHL tego nie robią i nie chcą zrobić.
katakrowa
@Dzyszla: W takim razie nie ma innego wyjścia. Jak się nie da to się zwolnij i idź do innej pracy albo zmień projekt. Widać trafiłeś na rzecz, która Cię przerosła mimo, że faktycznie jest normalną sprawą jaką można spotkać przy pracy z liczbami zmiennoprzecinkowymi zapisywanymi w pamięci w postaci cechy i mantysy. Jako programista powinieneś to wiedzieć i umieć obsłużyć.
Dzyszla
@katakrowa: Jako programista, powinieneś wiedzieć, na czym polega programowanie zgodne z zasadami SOLID. Wiec daruj obie takie ironie. Zadałem wprost pytanie - czy zna ktoś metodę na obejście ograniczenia bez modyfikacji kodu tworzonego automatycznie i bez potrzeby redefinicji kodu utworzonego czy edycji definicji WSDL.
katakrowa
@Dzyszla: ojejku zmienna przyszła w złym formacie a Ty nie wiesz co z tym zrobić... Napisali Ci ale to olewasz - zatem męcz się z tym sam.
Dzyszla
Nie przyszła, tylko wychodzi! Jak nie umiesz czytać ze zrozumieniem i nie znasz odpowiedzi to się nie udzielaj!
Manna5
  • Rejestracja:około 6 lat
  • Ostatnio:dzień
  • Lokalizacja:Kraków
  • Postów:641
0

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


Dzyszla
To jest zapisywane wartościowo, nie tekstowo. Tekst powstaje już przez klasy bazowe. Ponadto to nie obcięcie, bo może też dochodzić do przekłamań w drugą stronę. To musi być zaokrąglenie.
Schadoow
  • Rejestracja:ponad 13 lat
  • Ostatnio:3 minuty
  • Postów:1068
1

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

Kopiuj
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))
}
Zobacz pozostałe 4 komentarze
Schadoow
Ehh czyli nie masz wrappera. Tylko jedziesz na API firmy zewnetrznej.
Schadoow
No to nauczka na przyszłosc. Nigdy nie ufaj nikomu nawet sobie. I całą ale to CAŁĄ komunikacje z zewnętrznymi serwisami zawsze wrapuj wewnątrz uchroni cię to przed zmianami. I da możliwość utrzymania kompatybilności wstecznej.
Schadoow
Żeby nie było to nie tylko kwestia DHL, taki facebook jak nie płacisz mu za biznesowe api potrafi przerazac api pare razy w ciagu tygodnia. I to jeszcze wstecz tj łamie swoje wersjonowanie api.
Dzyszla
Tak się kończy "szybko i prosto" :) Natomiast wszystko cacy, gdyby nie tak naprawdę błąd funkcjonujący po obu stronach i dopiero w połączeniu powoduje problemy.
MR
  • Rejestracja:około 15 lat
  • Ostatnio:7 dni
0

Tak na szybko gotowiec

Kopiuj
var
  DHL_kwota :Single;
begin
      DHL_kwota:=0.9900004564;

      label3.Caption:=(FormatFloat('0.00', DHL_kwota));
Dzyszla
Tak to ja umiem - chodzi o to, jak środowisko dokonuje zamian zapisując do XML z klas, które generuje na podstawie WSDL (komponent THTTPRIO, który z kolei bazuje na obiekcie bazującym na RTTI - ciężko to debugować nawet).
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:około godziny
  • Lokalizacja:Tuchów
  • Postów:12175
2

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


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
robertz68
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Zielona Góra
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:

Kopiuj
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ą:

Kopiuj
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.

Kopiuj
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;
Dzyszla
Absolutna racja. Zaokrąglanie to osobna bajka w Delphi. Na szczęście tu problem nie istnieje, bo błędy pojawiają się dość daleko, niemniej nie rozumiem, po co zapisując do XML używana jest dokładność razem z błędami, podczas gdy zwykły floatToStr zrobi poprawnie tą samą wartość.
_13th_Dragon
  • Rejestracja:prawie 20 lat
  • Ostatnio:12 dni
1

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
Dzyszla
  • Rejestracja:około 3 lata
  • Ostatnio:około 5 godzin
  • Postów:108
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)::

Kopiuj
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;```
edytowany 1x, ostatnio: Dzyszla
Dzyszla
  • Rejestracja:około 3 lata
  • Ostatnio:około 5 godzin
  • Postów:108
0

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

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

W wyniku dostajemy:

Kopiuj
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).

MR
  • Rejestracja:ponad 4 lata
  • Ostatnio:około rok
  • Postów:8
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ł

robertz68
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 8 godzin
  • Lokalizacja:Zielona Góra
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ć.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.