[Delphi] Jak zwiększyć dokładność wyniku działania matemat.

0

Co do nazwy tematu, nie bardzo wiedziałem jak go sformułować, mam nadzieje, że pasuje. :-P

Otóż zacząłem sobie pisać kalkulator na wzór systemowego MS Kalkulatora. I mam funkcję obliczania silni. Świeci ona swoją prostotą, ale daje w miarę odpowiedni wynik:

function TMainForm.Silnia(n: Integer): Extended;
var
  i: Integer;
begin
  Result := 1;
  for i := 1 to n do
    Result := Result * i;
end;

używam jej w taki sposób:

procedure TMainForm.bSilniaClick(Sender: TObject);
begin
  MEdit.Text := FloatToStrF
    (Silnia(Round(StrToFloat(MEdit.Text))), ffGeneral, 18, 32);
end;

Ale to nie jest problemem, a funkcję i tak będę musiał potem nieco zmodyfikować...

Przechodząc do sedna problemu. Obliczając 24! przy pomocy tej funkcji otrzymuje następujące wyniki:

  • używając FloatToStr: 6,20448401733239E23
  • używając FloatToStrF: 6,20448401733239439E23.
    Zaś w MS Kalkulatorze: 620 448 401 733 239 439 360 000.

Wszystkie wyniki są poprawne, jednakże wolałbym uzyskać ten sam co w systemowym kalkulatorze.
A otrzymuje w najlepszym wypadku 6,20448401733239439E23, czyli 620 448 401 733 239 439 000 000.

Stąd moje pytanie: Jak uzyskać dokładniejszy wynik? Proszę o wskazówki.

620 448 401 733 239 439 000 000
620 448 401 733 239 439 360 000

0

Należy zauważyć, iż wynik zwrócony przez kalkulator Microsoftu jest sformatowany, czyli zawiera separatory (odstęp co trzy cyfry). Aby uzyskać taki rezultat najłatwiej jest operować na łańcuchu znaków. Umożliwi on nie tylko wstawienie separatorów w liczbę, ale może także pomieścić liczby o 255 cyfrach. Poniżej zamieszczam kod źródłowy, który policzy silnie dla liczb dwucyfrowych.

Function User(S : String; n : Integer) : String;
Var
 I, w : Integer;
 Tmp : String;
Begin
 w:= 0;
 SetLength(Result, 0);
 For I:= Length(S) DownTo 1 Do
 Begin
  Str((Ord(S[I]) - 48) * n + w, Tmp);
  If Length(Tmp) > 1 Then
  Begin
   w:= StrToInt(Copy(Tmp, 1, Pred(Length(Tmp))));
   Tmp:= Tmp[Length(Tmp)];
  End Else w:= 0;
  Result:= Concat(Result, Tmp)
 End;
 SetLength(Tmp, 0);
 If w > 0 Then Tmp:= IntToStr(w);
 For I:= Length(Result) DownTo 1 Do Tmp:= Concat(Tmp, Result[I]);
 Result:= Tmp
End;  

Function Silnia(n : Integer) : String;
Var I : Integer;
Begin
 Result:= '1';
 For I:= 1 To n Do Result:= User(Result, I);
End;

To powinno rozwiązać problem z dokładnością wyniku, z separatorami powinieneś sam sobie poradzić.

PS: Gdyby nie wystarczyło 255 cyfr, użyj typu WideString.

0

Prosiłem raczej o wskazówkę, lubię dochodzić sam do czegoś :P aczkolwiek gotowcem nie pogardzę.
Przeanalizuje i też będzie dobrze. Dzięki za prawidłowe rozwiązanie problemu. Twój kod liczy nie tylko silnie dla liczb dwucyfrowych.
A z separatorami se poradzę, tylko specjalnie je użyłem, żeby lepiej był widoczny ten błąd moich obliczeń.
Jeszcze raz dzięki.

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