Kalkulator bez ONP

Deti

Poniżej zamieszczam funkcję własnej roboty, czyli kalkulator bez użycia odwrotnej notacji polskiej - sama zasada działania opiera się o tzw. "atomizację" wyrażeń w proste wartości, aż bedzie mozna na nich wykonywac podstawowe operacje. Metoda zapewne nie jest najszybsza, ale zawsze chciałem takie coś napisać :] Z biegiem czasu postaram się ją bardziej unowocześnić..

Wymaga modułu Math..

function Interpreter(Input: string): string;
(******************************************************************************
uses Math require !

function Interpreter(Input: string): string;
--------------------------------------------

[   v 0.9.1   ]]
================

This code is owned by HAKGERSoft, any modifications without HAKGERSoft
permision is prohibited!

Author:
  DetoX [[email protected]]
===============================================================================

Funkcja realizuje obliczenia matematyczne z danego wyrażenia np: 4+(4*sin(5-2))
Podany algorytm jest własnością HAKGERSoft - opiera się na atomizacji wyrażenia
i nie wykorzystuje odwrotnej notacji polskiej.

(TODO)
 - odporność na dzielenie przez zero
 - odporność przed przekroczeniem zakresu funkcji arcus, np. arcsin(7)
 - bardziej szczegółowe sprawdzenie poprawności zapisu wyrażenia
 - można dodać więcej funkcji

*******************************************************************************)

const
  H_NULL = '';
  H_PRECISION = '0.00000000000000000000'; // Dokładność obliczeń
  H_RESULT = '0.###'; // Zaokrąglenie wyniku
  H_POINT = ',';
  H_B1 = '('; // Znak otwarcia nawiasa - start nowego "atomu"
  H_B2 = ')'; // Znak zamknięcia nawiasa - koniec "atomu"
  H_PI = 3.14159265358979323846; // Wartość liczby Pi
  //H_ERROR = 'Błąd składni'; // Błąd
  
  Numbers: set of char = ['0'..'9']; // Cyfry
  Letters: set of char = ['a'..'z', 'A'..'Z', 'ł', 'ą', 'ę', 'ś', 'ć', 'ń', 'ź', 'ż', 'ó', 'Ł', 'Ą', 'Ę', 'Ś', 'Ć', 'Ń', 'Ź', 'Ż', 'Ó']; // Litery alfabetu
  BasicOpr: array [1..5] of Char = ('^', '*', '/', '+', '-'); // Podstawowe operatory matematyczne
  (* Powyższa kolejność elementów w tablicy decyduje o kolejności wykonywania działań! *)
  MathFunc: array [1..11] of string = ('arcsin', 'arccos', 'arctg', 'arcctg', 'sin', 'cos', 'tg', 'ctg', 'ln', 'exp', 'sqrt'); // Funkcje
  Atom_Begin: array [1..2] of Char = ('[', '{'); // Nawiasy otwierające
  Atom_End: array [1..2] of Char = (']', '}'); // Nawiasy zamykające

  function Optimize(Input: string): string;
  var
    i: Integer;
  begin
    Input := Trim(AnsiLowerCase(Input));
    Input := StringReplace(Input, ' ', H_NULL, [rfReplaceAll]);
    for i := Low(Atom_Begin) to High(Atom_Begin) do
      Input := StringReplace(Input, Atom_Begin[i], H_B1, [rfReplaceAll]);
    for i := Low(Atom_End) to High(Atom_End) do
      Input := StringReplace(Input, Atom_End[i], H_B2, [rfReplaceAll]);
    Input := StringReplace(Input, '.', ',', [rfReplaceAll]);
    Result := Input;
  end;

  function Valid(Input: string): Boolean;
  var
    i, B1_Count, B2_Count: Integer;
  begin
    Result := True;
    B1_Count := 0;
    B2_Count := 0;
    for i := 1 to length(Input) do
      if Input[i] = H_B1 then
        Inc(B1_Count)
      else if Input[i] = H_B2 then
        Inc(B2_Count);
    Result := Boolean(B1_Count = B2_Count);

    for i := Low(MathFunc) to High(MathFunc) do
      Input := StringReplace(Input, MathFunc[i], H_NULL, [rfReplaceAll]);
    for i := 1 to length(Input) do
      if Input[i] in Letters then
        Result := False;
  end;

  function RightValue(Input: string): string;
  var
    i: Integer;
  begin
    Result := H_NULL;
    for i := 1 to length(Input) do
    begin
      if (Input[i] in Numbers) or (Input[i] = H_POINT) or ((Input[i] = '-') and (i = 1)) then
        Result := Result + Input[i]
      else
        Break;
    end;
  end;

  function LeftValue(Input: string): string;
  var
    i: Integer;
    Reverse: string;
  begin
    Reverse := H_NULL;
    Result := H_NULL;
    for i := length(Input) downto 1 do
    begin
      if (Input[i] in Numbers) or (Input[i] = H_POINT) then
        Reverse := Reverse + Input[i]
      else if (Input[i] = '-') then
      begin
        if i < length(Input) then
          if Input[i + 1] in Numbers then
          begin
            Reverse := Reverse + Input[i];
            Break;
          end;
      end
      else
        Break;
    end;
    for i := length(Reverse) downto 1 do
      Result := Result + Reverse[i];
  end;
  
  function GetBracket(Input: string): Integer;
  var
    i: Integer;
    Counter: ShortInt;
  begin
    Counter := 0;
    for i := 1 to length(Input) do
    begin
      if Input[i] = H_B1 then
        Counter := Counter + 1
      else if (Input[i] = H_B2) and (Counter > 0) then
        Counter := Counter - 1
      else if (Input[i] = H_B2) and (Counter = 0) then
      begin
        Result := i;
        Break;
      end;
    end;
  end;

  function FunctionAtomize(Value: string; FunctionIndex: Integer): string;
  var
    V, Return: Extended;
  begin
    V := StrToFloat(Value);
    case FunctionIndex of
      1: Return := Arcsin(V); // Sinus [ dla radiana ]
      2: Return := Arccos(V); // Cosinus [ dla radiana ]
      3: Return := Arctan(V); // Tangens [ dla radiana ]
      4: Return := Arccot(V); // Cotangens [ dla radiana ]
      5: Return := Sin(V); // Arcus sinus [ dla radiana ]
      6: Return := Cos(V); // Arcus cosinus [ dla radiana ]
      7: Return := Tan(V); // Arcus tangens [ dla radiana ]
      8: Return := Cotan(V); // Arcus cotangens [ dla radiana ]
      9: Return := Ln(V); // Logarytm naturalny
      10: Return := Exp(V); // Exponent
      11: Return := sqrt(V); // Pierwiastek
      // Można dodać nowe funkcje
    end;
    Result := FormatFloat(H_PRECISION, Return);
  end;

  function ValueAtomize(Value1, Value2: string; FunctionOperator: Char): string;
    var
      V1, V2, Return: Extended;
    begin
      V1 := StrToFloat(Value1);
      V2 := StrToFloat(Value2);
      case FunctionOperator of
        '+': Return := V1 + V2;
        '-': Return := V1 - V2;
        '*': Return := V1 * V2;
        '/': Return := V1 / V2;
        '^': Return := Power(V1, V2);
      end;
      Result := FormatFloat(H_PRECISION, Return);
    end;

  function SimplyCount(Input: string): string;
  var
    i: Integer;
    Value: string;
  begin
    while pos('--', Input) > 0 do
      Input := StringReplace(Input, '--', '+', []);
    for i := Low(MathFunc) to High(MathFunc) do
    begin
      while pos(MathFunc[i], Input) > 0 do
      begin
        Value := RightValue(Copy(Input, pos(MathFunc[i], Input) + length(MathFunc[i]), length(Input) - pos(MathFunc[i], Input) - length(MathFunc[i]) + 1));
        Input := StringReplace(Input, MathFunc[i] + Value, FunctionAtomize(Value, i), []); 
        while pos('--', Input) > 0 do
          Input := StringReplace(Input, '--', '+', []);
      end;
    Result := Input;
    end;
  end;

  function OnlyBasic(Input: string): string;
  var
    Value1, Value2: string;
    i: Integer;
  begin
    while pos('--', Input) > 0 do
      Input := StringReplace(Input, '--', '+', []);
    for i := Low(BasicOpr) to High(BasicOpr) do
      while pos(BasicOpr[i], Input) > 1 do
      begin
        Value1 := LeftValue(Copy(Input, 1, pos(BasicOpr[i], Input) - 1));
        Value2 := RightValue(Copy(Input, pos(BasicOpr[i], Input) + 1, length(Input) - pos(BasicOpr[i], Input)));
        Input := StringReplace(Input, Value1 + BasicOpr[i] + Value2, ValueAtomize(Value1, Value2, BasicOpr[i]), []);
        while pos('--', Input) > 0 do
          Input := StringReplace(Input, '--', '+', []);
      end;
    Result := FormatFloat(H_PRECISION, StrToFloat(Input));
  end;

  function AtomIntoValue(Input: string): string;
  begin
    while pos(H_B1, Input) > 0 do
      Input := StringReplace(Input, H_B1 + Copy(Input,  pos(H_B1, Input) + 1, GetBracket(Copy(Input, pos(H_B1, Input) + 1, length(Input) - pos(H_B1, Input))) - 1) + H_B2, AtomIntoValue(Copy(Input, pos(H_B1, Input) + 1, GetBracket(Copy(Input, pos(H_B1, Input) + 1, length(Input) - pos(H_B1, Input))) - 1)), []);
    Result := SimplyCount(Input);
    Result := OnlyBasic(Result);
  end;

begin
  Result := Optimize(Input); // Optymalizacja
  if Valid(Result) then // Walidacja
    Result := FormatFloat(H_RESULT, StrToFloat(AtomIntoValue(Result))); 
end;

Dla sprawdzenia, dajcie jakieś obliczenia np:

((6103)/(6,310</sup>3))((1,1)/(1+0,15sin(arccos(0,85))))

AHA: aby zadziałała - to wyrażenie musi być poprawne :) - ale to chyba oczywiste. Zaletą takiej funkcji jest to, że bardzo łatwo dodać nowe funkcje ... - co zresztą widać..

5 komentarzy

Jeszcze jeden błąd : kolejność działań w interpreterze jest taka : potęgowanie, mnożenie, dzielenie, dodawanie, odejmowanie. A mnożenie i dzielenie powinno być na tym samym poziomie ( a nie najpierw mnożenie ) . Powoduje to błąd np. w wyrażeniu
2/2*2 - interpreter daje wynik 0.5 ( bo najpierw mnoży ). Prawidłowy wynik to 2

Fajna sprawa. Odkryłem jednak 2 błędy :
-Przy operacjach typu -3-2 ( znajduje jako pierwszy operator pierszy minus i lewy argument jest pusty )

  • Niepoprawne wykonywanie operacji typu 9-3+4 ( najpier robi dodawnie, wiewc wynik -3+4 jest 1 i znika operator, wiec zlacza stringi i wychodzi 91 )
    Poprawienie obu błędów zajęło mi ok. godziny.
    Anyway gratuluje autorowi, bo fajnie jest to napisane :)

Chyba zostanę przy ONP, ale bravo za chęci :D

hyh gratulacje nastepny artykul napisany przez dobrego programiste oby tak dalej;)