Bagaimana cara mengevaluasi ekspresi matematika yang diberikan dalam bentuk string?


318

Saya mencoba menulis rutin Java untuk mengevaluasi ekspresi matematika sederhana dari Stringnilai-nilai seperti:

  1. "5+3"
  2. "10-40"
  3. "10*3"

Saya ingin menghindari banyak pernyataan if-then-else. Bagaimana saya bisa melakukan ini?


7
Saya baru-baru ini menulis parser ekspresi matematika bernama exp4j yang dirilis di bawah lisensi apache Anda dapat memeriksanya di sini: objecthunter.net/exp4j
fasseg

2
Apa jenis ekspresi yang Anda izinkan? Hanya ekspresi operator tunggal? Apakah tanda kurung diizinkan?
Raedwald


1
Kemungkinan rangkap dari apakah ada fungsi eval () di Java?
Andrew Li

3
Bagaimana ini bisa dianggap terlalu luas? Evaluasi Dijkstra adalah solusi yang jelas di sini en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Jawaban:


376

Dengan JDK1.6, Anda dapat menggunakan mesin Javascript bawaan.

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class Test {
  public static void main(String[] args) throws ScriptException {
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("JavaScript");
    String foo = "40+2";
    System.out.println(engine.eval(foo));
    } 
}

52
Sepertinya ada masalah besar di sana; Itu mengeksekusi skrip, bukan mengevaluasi ekspresi. Agar jelas, engine.eval ("8; 40 + 2"), output 42! Jika Anda ingin parser ekspresi yang juga memeriksa sintaks, saya baru saja menyelesaikan satu (karena saya tidak menemukan apa pun yang sesuai dengan kebutuhan saya): Javaluator .
Jean-Marc Astesana

4
Sebagai catatan tambahan, jika Anda perlu menggunakan hasil dari ekspresi ini di tempat lain dalam kode Anda, Anda dapat mengetikkan hasilnya menjadi ganda seperti: return (Double) engine.eval(foo);
Ben Visness

38
Catatan keamanan: Anda tidak boleh menggunakan ini dalam konteks server dengan input pengguna. JavaScript yang dieksekusi dapat mengakses semua kelas Java dan dengan demikian membajak aplikasi Anda tanpa batas.
Boann

3
@Boann, saya meminta Anda untuk memberi saya referensi tentang apa yang Anda katakan. (Pastinya 100%)
partho

17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- akan menulis file melalui JavaScript ke (secara default) direktori saat ini program
Boann

236

Saya telah menulis evalmetode ini untuk ekspresi aritmatika untuk menjawab pertanyaan ini. Itu penambahan, pengurangan, perkalian, pembagian, eksponensial (menggunakan ^simbol), dan beberapa fungsi dasar seperti sqrt. Ini mendukung pengelompokan menggunakan (... ), dan itu membuat operator lebih diutamakan dan aturan associativity .

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)`
        //        | number | functionName factor | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                eat(')');
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                x = parseFactor();
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation

            return x;
        }
    }.parse();
}

Contoh:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));

Output: 7.5 (yang benar)


Parser adalah parser keturunan rekursif , sehingga secara internal menggunakan metode parse yang terpisah untuk setiap tingkat prioritas operator dalam tata bahasanya. Saya menyimpannya singkat sehingga mudah untuk dimodifikasi, tetapi berikut adalah beberapa ide yang mungkin ingin Anda kembangkan:

  • Variabel:

    Bit parser yang membaca nama untuk fungsi dapat dengan mudah diubah untuk menangani variabel kustom juga, dengan mencari nama dalam tabel variabel yang diteruskan ke evalmetode, seperti a Map<String,Double> variables.

  • Kompilasi dan evaluasi terpisah:

    Bagaimana jika, setelah menambahkan dukungan untuk variabel, Anda ingin mengevaluasi ekspresi yang sama jutaan kali dengan variabel yang berubah, tanpa menguraikannya setiap waktu? Itu mungkin. Pertama-tama tentukan antarmuka yang digunakan untuk mengevaluasi ekspresi yang dikompilasi:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Sekarang ubah semua metode yang mengembalikan doubles, jadi alih-alih mereka mengembalikan instance dari antarmuka itu. Sintaks lambda Java 8 sangat cocok untuk ini. Contoh salah satu metode yang diubah:

    Expression parseExpression() {
        Expression x = parseTerm();
        for (;;) {
            if (eat('+')) { // addition
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() + b.eval());
            } else if (eat('-')) { // subtraction
                Expression a = x, b = parseTerm();
                x = (() -> a.eval() - b.eval());
            } else {
                return x;
            }
        }
    }

    Itu membangun pohon Expressionobjek rekursif yang mewakili ekspresi dikompilasi ( pohon sintaksis abstrak ). Kemudian Anda dapat mengompilasinya sekali dan mengevaluasinya berulang kali dengan nilai yang berbeda:

    public static void main(String[] args) {
        Map<String,Double> variables = new HashMap<>();
        Expression exp = parse("x^2 - x + 2", variables);
        for (double x = -20; x <= +20; x++) {
            variables.put("x", x);
            System.out.println(x + " => " + exp.eval());
        }
    }
  • Tipe data yang berbeda:

    Alih-alih double, Anda bisa mengubah evaluator untuk menggunakan sesuatu yang lebih kuat seperti BigDecimal, atau kelas yang mengimplementasikan bilangan kompleks, atau bilangan rasional (pecahan). Anda bahkan dapat menggunakan Object, memungkinkan beberapa campuran tipe data dalam ekspresi, seperti bahasa pemrograman nyata. :)


Semua kode dalam jawaban ini dirilis ke domain publik . Selamat bersenang-senang!


1
Algoritma yang bagus, mulai dari itu saya berhasil operator impresi dan logis. Kami membuat kelas terpisah untuk fungsi untuk mengevaluasi fungsi, jadi seperti ide Anda tentang variabel, saya membuat peta dengan fungsi dan menjaga nama fungsi. Setiap fungsi mengimplementasikan antarmuka dengan metode eval (T rightOperator, T leftOperator), jadi kapan saja kita dapat menambahkan fitur tanpa mengubah kode algoritma. Dan itu ide yang bagus untuk membuatnya bekerja dengan tipe generik. Terimakasih!
Vasile Bors

1
Bisakah Anda menjelaskan logika di balik algoritma ini?
iYonatan

1
Saya mencoba memberikan deskripsi tentang apa yang saya pahami dari kode yang ditulis oleh Boann, dan contoh-contoh yang dijelaskan wiki. Logika algoritma ini mulai dari aturan perintah operasi. 1. tanda operator | evaluasi variabel | panggilan fungsi | tanda kurung (sub-ekspresi); 2. eksponensial; 3. perkalian, pembagian; 4. penambahan, pengurangan;
Vasile Bors

1
Metode algoritma dibagi untuk setiap tingkat urutan operasi sebagai berikut: parseFactor = 1. tanda operator | evaluasi variabel | panggilan fungsi | tanda kurung (sub-ekspresi); 2. eksponensial; parseTerms = 3. perkalian, pembagian; parseExpression = 4. Selain itu, kurangi. Algoritme, memanggil metode dengan urutan terbalik (parseExpression -> parseTerms -> parseFactor -> parseExpression (untuk sub-ekspresi)), tetapi setiap metode pada baris pertama memanggil metode ke tingkat berikutnya, sehingga seluruh metode urutan eksekusi akan menjadi urutan operasi sebenarnya normal.
Vasile Bors

1
Misalnya metode parseExpression, double x = parseTerm(); evaluasi operator kiri, setelah for (;;) {...}evaluasi ini, operasi sukses tingkat pemesanan aktual (penambahan, pengurangan). Logika yang sama adalah dan dalam metode parseTerm. The parseFactor tidak memiliki level berikutnya, jadi hanya ada evaluasi metode / variabel atau dalam kasus paranthesis - evaluasi sub-ekspresi. The boolean eat(int charToEat)Metode pemeriksaan kesetaraan karakter kursor saat ini dengan karakter charToEat, jika pengembalian sama benar dan pindah kursor ke karakter berikutnya, saya menggunakan nama 'menerima' untuk itu.
Vasile Bors

34

Cara yang benar untuk menyelesaikan ini adalah dengan lexer dan parser . Anda dapat menulis sendiri versi sederhana ini, atau halaman tersebut juga memiliki tautan ke Java lexers dan parser.

Membuat parser keturunan rekursif adalah latihan pembelajaran yang sangat baik.


26

Untuk proyek universitas saya, saya mencari parser / evaluator yang mendukung rumus dasar dan persamaan yang lebih rumit (terutama operator yang diiterasi). Saya menemukan perpustakaan open source yang sangat bagus untuk JAVA dan .NET disebut mXparser. Saya akan memberikan beberapa contoh untuk membuat beberapa perasaan pada sintaks, untuk instruksi lebih lanjut silakan kunjungi situs web proyek (terutama bagian tutorial).

https://mathparser.org/

https://mathparser.org/mxparser-tutorial/

https://mathparser.org/api/

Dan beberapa contoh

1 - Furmula sederhana

Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()

2 - Argumen dan konstanta yang ditentukan pengguna

Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()

3 - Fungsi yang ditentukan pengguna

Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()

4 - Iterasi

Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()

Ditemukan baru-baru - dalam kasus Anda ingin mencoba sintaks (dan melihat kasus penggunaan lanjutan) Anda dapat men-download Scalar Kalkulator aplikasi yang didukung oleh mXparser.

salam Hormat


Sejauh ini adalah perpustakaan matematika terbaik di luar sana; sederhana untuk memulai, mudah digunakan dan dapat diperpanjang. Pasti harus menjadi jawaban teratas.
Trynkiewicz Mariusz

Temukan versi Maven di sini .
izogfif

Saya menemukan mXparser tidak dapat mengidentifikasi formula ilegal, misalnya, '0/0' akan mendapatkan hasil sebagai '0'. Bagaimana saya bisa mengatasi masalah ini?
lulijun

Baru saja menemukan solusinya, expression.setSlientMode ()
lulijun

20

HERE adalah pustaka sumber terbuka lain di GitHub bernama EvalEx.

Berbeda dengan mesin JavaScript, perpustakaan ini hanya berfokus dalam mengevaluasi ekspresi matematika saja. Selain itu, perpustakaan dapat diperluas dan mendukung penggunaan operator boolean serta tanda kurung.


Ini tidak masalah, tetapi gagal ketika kami mencoba mengalikan nilai kelipatan 5 atau 10, misalnya 65 * 6 menghasilkan 3,9E + 2 ...
paarth batra

Tapi ada cara untuk memperbaikinya dengan melemparkannya ke int yaitu int output = (int) 65 * 6 sekarang akan menghasilkan 390
paarth batra

1
Untuk memperjelas, itu bukan masalah perpustakaan melainkan masalah dengan representasi angka sebagai nilai floating point.
DavidBittner

Perpustakaan ini sangat bagus. @paarth batra Casting ke int akan menghapus semua titik desimal. Gunakan ini sebagai gantinya: expression.eval (). ToPlainString ();
einUsername


14

Anda dapat mengevaluasi ekspresi dengan mudah jika aplikasi Java Anda sudah mengakses database, tanpa menggunakan JAR lainnya.

Beberapa database mengharuskan Anda untuk menggunakan tabel dummy (misalnya, tabel "ganda" Oracle) dan yang lain akan memungkinkan Anda untuk mengevaluasi ekspresi tanpa "memilih" dari tabel mana pun.

Misalnya, di Sql Server atau Sqlite

select (((12.10 +12.0))/ 233.0) amount

dan di Oracle

select (((12.10 +12.0))/ 233.0) amount from dual;

Keuntungan menggunakan DB adalah Anda dapat mengevaluasi banyak ekspresi secara bersamaan. Juga sebagian besar DB akan memungkinkan Anda untuk menggunakan ekspresi yang sangat kompleks dan juga akan memiliki sejumlah fungsi tambahan yang dapat dipanggil seperlunya.

Namun kinerja dapat menderita jika banyak ekspresi tunggal perlu dievaluasi secara individual, terutama ketika DB terletak di server jaringan.

Berikut ini mengatasi masalah kinerja sampai batas tertentu, dengan menggunakan database di memori Sqlite.

Berikut ini contoh kerja lengkap di Jawa

Class. forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");
rs.next();
System.out.println(rs.getBigDecimal(1));
stat.close();
conn.close();

Tentu saja Anda dapat memperluas kode di atas untuk menangani banyak perhitungan secara bersamaan.

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");

5
Katakan halo pada injeksi SQL!
cyberz

Tergantung pada apa Anda menggunakan DB. Jika Anda ingin memastikan, Anda dapat dengan mudah membuat DB sqlite kosong, khusus untuk evaluasi matematika.
DAB

4
@cyberz Jika Anda menggunakan contoh saya di atas, Sqlite akan membuat DB sementara di memori. Lihat stackoverflow.com/questions/849679/…
DAB

11

Artikel ini membahas berbagai pendekatan. Berikut adalah 2 pendekatan utama yang disebutkan dalam artikel:

JEXL dari Apache

Mengizinkan skrip yang menyertakan referensi ke objek java.

// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "foo.innerFoo.bar()";
Expression e = jexl.createExpression( jexlExp );
 
// Create a context and add data
JexlContext jctx = new MapContext();
jctx.set("foo", new Foo() );
 
// Now evaluate the expression, getting the result
Object o = e.evaluate(jctx);

Gunakan mesin javascript yang tertanam di JDK:

private static void jsEvalWithVariable()
{
    List<String> namesList = new ArrayList<String>();
    namesList.add("Jill");
    namesList.add("Bob");
    namesList.add("Laureen");
    namesList.add("Ed");
 
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");
 
    jsEngine.put("namesListKey", namesList);
    System.out.println("Executing in script environment...");
    try
    {
      jsEngine.eval("var x;" +
                    "var names = namesListKey.toArray();" +
                    "for(x in names) {" +
                    "  println(names[x]);" +
                    "}" +
                    "namesListKey.add(\"Dana\");");
    }
    catch (ScriptException ex)
    {
        ex.printStackTrace();
    }
}

4
Harap rangkum informasi dari artikel, jika tautannya rusak.
DJClayworth

Saya telah memutakhirkan jawaban untuk menyertakan bit yang relevan dari artikel
Brad Parks

1
dalam praktiknya, JEXL lambat (menggunakan introspeksi kacang), memiliki masalah kinerja dengan multithreading (cache global)
Nishi

Senang tahu @Nishi! - Kasus penggunaan saya adalah untuk debugging hal-hal di lingkungan hidup, tetapi bukan bagian dari aplikasi yang digunakan normal.
Brad Parks

10

Cara lain adalah dengan menggunakan Spring Expression Language atau SpEL yang jauh lebih banyak bersama dengan mengevaluasi ekspresi matematika karena itu mungkin sedikit berlebihan. Anda tidak harus menggunakan kerangka kerja Spring untuk menggunakan pustaka ekspresi ini karena itu berdiri sendiri. Menyalin contoh dari dokumentasi SpEL:

ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0

Baca contoh SpEL lebih ringkas di sini dan dokumen selengkapnya di sini


8

jika kita akan mengimplementasikannya maka kita dapat menggunakan algoritma di bawah ini: -

  1. Meskipun masih ada token yang harus dibaca,

    1.1 Dapatkan token berikutnya. 1.2 Jika tokennya adalah:

    1.2.1 Nomor: dorong ke tumpukan nilai.

    1.2.2 Variabel: dapatkan nilainya, dan dorong ke tumpukan nilai.

    1.2.3 Tanda kurung kiri: dorong ke tumpukan operator.

    1.2.4 Tanda kurung yang tepat:

     1 While the thing on top of the operator stack is not a 
       left parenthesis,
         1 Pop the operator from the operator stack.
         2 Pop the value stack twice, getting two operands.
         3 Apply the operator to the operands, in the correct order.
         4 Push the result onto the value stack.
     2 Pop the left parenthesis from the operator stack, and discard it.

    1.2.5 Seorang operator (sebut saja iniOp):

     1 While the operator stack is not empty, and the top thing on the
       operator stack has the same or greater precedence as thisOp,
       1 Pop the operator from the operator stack.
       2 Pop the value stack twice, getting two operands.
       3 Apply the operator to the operands, in the correct order.
       4 Push the result onto the value stack.
     2 Push thisOp onto the operator stack.
  2. Meskipun tumpukan operator tidak kosong, 1 Angkat operator dari tumpukan operator. 2 Pop nilai stack dua kali, dapatkan dua operan. 3 Terapkan operator ke operan, dalam urutan yang benar. 4 Dorong hasilnya ke tumpukan nilai.

  3. Pada titik ini tumpukan operator harus kosong, dan tumpukan nilai harus hanya memiliki satu nilai di dalamnya, yang merupakan hasil akhir.


3
Ini adalah eksposisi yang tidak terakreditasi dari algoritma Dijkstra Shunting-yard . Kredit di mana kredit jatuh tempo.
Marquis of Lorne



4

Saya pikir apa pun cara Anda melakukan ini akan melibatkan banyak pernyataan bersyarat. Tetapi untuk operasi tunggal seperti dalam contoh Anda, Anda dapat membatasi hingga 4 jika pernyataan dengan sesuatu seperti

String math = "1+4";

if (math.split("+").length == 2) {
    //do calculation
} else if (math.split("-").length == 2) {
    //do calculation
} ...

Itu menjadi jauh lebih rumit ketika Anda ingin berurusan dengan beberapa operasi seperti "4 + 5 * 6".

Jika Anda mencoba membangun kalkulator maka saya lebih baik melewati setiap bagian perhitungan secara terpisah (setiap angka atau operator) daripada sebagai string tunggal.


2
Ini menjadi jauh lebih rumit segera setelah Anda harus berurusan dengan beberapa operasi, prioritas operator, tanda kurung, ... pada kenyataannya apa pun yang menjadi ciri ekspresi aritmatika nyata. Anda tidak bisa sampai di sana mulai dari teknik ini.
Marquis of Lorne

4

Sudah terlambat untuk menjawab tetapi saya menemukan situasi yang sama untuk mengevaluasi ekspresi di java, mungkin membantu seseorang

MVELtidak evaluasi runtime ekspresi, kita bisa menulis kode java Stringuntuk mendapatkannya dievaluasi dalam hal ini.

    String expressionStr = "x+y";
    Map<String, Object> vars = new HashMap<String, Object>();
    vars.put("x", 10);
    vars.put("y", 20);
    ExecutableStatement statement = (ExecutableStatement) MVEL.compileExpression(expressionStr);
    Object result = MVEL.executeExpression(statement, vars);

Saya menjelajahi dan menemukan beberapa fungsi Aritmatika tambahan yang juga ditangani di sini github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
thecodefather

Awsome! Itu menyelamatkan hari saya. Terima kasih
Sarika.S

4

Anda mungkin melihat kerangka kerja Symja :

ExprEvaluator util = new ExprEvaluator(); 
IExpr result = util.evaluate("10-40");
System.out.println(result.toString()); // -> "-30" 

Perhatikan bahwa ekspresi yang pasti lebih kompleks dapat dievaluasi:

// D(...) gives the derivative of the function Sin(x)*Cos(x)
IAST function = D(Times(Sin(x), Cos(x)), x);
IExpr result = util.evaluate(function);
// print: Cos(x)^2-Sin(x)^2

4

Coba kode contoh berikut menggunakan mesin Javascript JDK1.6 dengan penanganan injeksi kode.

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class EvalUtil {
private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static void main(String[] args) {
    try {
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || 5 >3 "));
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || true"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public Object eval(String input) throws Exception{
    try {
        if(input.matches(".*[a-zA-Z;~`#$_{}\\[\\]:\\\\;\"',\\.\\?]+.*")) {
            throw new Exception("Invalid expression : " + input );
        }
        return engine.eval(input);
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
 }
}

4

Ini sebenarnya melengkapi jawaban yang diberikan oleh @Boann. Ini memiliki sedikit bug yang menyebabkan "-2 ^ 2" memberikan hasil yang salah dari -4.0. Masalah untuk itu adalah titik di mana eksponensial dievaluasi dalam bukunya. Pindahkan eksponensial ke blok parseTerm (), dan Anda akan baik-baik saja. Lihat di bawah ini, yang merupakan jawaban @ Boann sedikit dimodifikasi. Modifikasi ada di komentar.

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)`
        //        | number | functionName factor | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                eat(')');
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                x = parseFactor();
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            //if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation -> This is causing a bit of problem

            return x;
        }
    }.parse();
}

-2^2 = -4sebenarnya normal, dan bukan bug. Itu akan dikelompokkan seperti -(2^2). Cobalah di Desmos, misalnya. Kode Anda sebenarnya memperkenalkan beberapa bug. Yang pertama adalah bahwa ^tidak ada lagi grup yang kanan-ke-kiri. Dengan kata lain, 2^3^2seharusnya dikelompokkan sebagai suka 2^(3^2)karena ^asosiatif yang benar, tetapi modifikasi Anda membuatnya suka (2^3)^2. Yang kedua adalah yang ^seharusnya memiliki prioritas lebih tinggi daripada *dan /, tetapi modifikasi Anda memperlakukannya sama. Lihat ideone.com/iN2mMa .
Radiodef

Jadi, apa yang Anda sarankan adalah bahwa eksponensial lebih baik disimpan di tempat yang semula bukan?
Romeo Sierra

Ya, itulah yang saya sarankan.
Radiodef

4
package ExpressionCalculator.expressioncalculator;

import java.text.DecimalFormat;
import java.util.Scanner;

public class ExpressionCalculator {

private static String addSpaces(String exp){

    //Add space padding to operands.
    //https://regex101.com/r/sJ9gM7/73
    exp = exp.replaceAll("(?<=[0-9()])[\\/]", " / ");
    exp = exp.replaceAll("(?<=[0-9()])[\\^]", " ^ ");
    exp = exp.replaceAll("(?<=[0-9()])[\\*]", " * ");
    exp = exp.replaceAll("(?<=[0-9()])[+]", " + "); 
    exp = exp.replaceAll("(?<=[0-9()])[-]", " - ");

    //Keep replacing double spaces with single spaces until your string is properly formatted
    /*while(exp.indexOf("  ") != -1){
        exp = exp.replace("  ", " ");
     }*/
    exp = exp.replaceAll(" {2,}", " ");

       return exp;
}

public static Double evaluate(String expr){

    DecimalFormat df = new DecimalFormat("#.####");

    //Format the expression properly before performing operations
    String expression = addSpaces(expr);

    try {
        //We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and
        //subtraction will be processed in following order
        int indexClose = expression.indexOf(")");
        int indexOpen = -1;
        if (indexClose != -1) {
            String substring = expression.substring(0, indexClose);
            indexOpen = substring.lastIndexOf("(");
            substring = substring.substring(indexOpen + 1).trim();
            if(indexOpen != -1 && indexClose != -1) {
                Double result = evaluate(substring);
                expression = expression.substring(0, indexOpen).trim() + " " + result + " " + expression.substring(indexClose + 1).trim();
                return evaluate(expression.trim());
            }
        }

        String operation = "";
        if(expression.indexOf(" / ") != -1){
            operation = "/";
        }else if(expression.indexOf(" ^ ") != -1){
            operation = "^";
        } else if(expression.indexOf(" * ") != -1){
            operation = "*";
        } else if(expression.indexOf(" + ") != -1){
            operation = "+";
        } else if(expression.indexOf(" - ") != -1){ //Avoid negative numbers
            operation = "-";
        } else{
            return Double.parseDouble(expression);
        }

        int index = expression.indexOf(operation);
        if(index != -1){
            indexOpen = expression.lastIndexOf(" ", index - 2);
            indexOpen = (indexOpen == -1)?0:indexOpen;
            indexClose = expression.indexOf(" ", index + 2);
            indexClose = (indexClose == -1)?expression.length():indexClose;
            if(indexOpen != -1 && indexClose != -1) {
                Double lhs = Double.parseDouble(expression.substring(indexOpen, index));
                Double rhs = Double.parseDouble(expression.substring(index + 2, indexClose));
                Double result = null;
                switch (operation){
                    case "/":
                        //Prevent divide by 0 exception.
                        if(rhs == 0){
                            return null;
                        }
                        result = lhs / rhs;
                        break;
                    case "^":
                        result = Math.pow(lhs, rhs);
                        break;
                    case "*":
                        result = lhs * rhs;
                        break;
                    case "-":
                        result = lhs - rhs;
                        break;
                    case "+":
                        result = lhs + rhs;
                        break;
                    default:
                        break;
                }
                if(indexClose == expression.length()){
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose);
                }else{
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose + 1);
                }
                return Double.valueOf(df.format(evaluate(expression.trim())));
            }
        }
    }catch(Exception exp){
        exp.printStackTrace();
    }
    return 0.0;
}

public static void main(String args[]){

    Scanner scanner = new Scanner(System.in);
    System.out.print("Enter an Mathematical Expression to Evaluate: ");
    String input = scanner.nextLine();
    System.out.println(evaluate(input));
}

}


1
Tidak menangani prioritas operator, atau beberapa operator, atau tanda kurung. Jangan gunakan.
Marquis of Lorne

2

Bagaimana dengan sesuatu yang seperti ini:

String st = "10+3";
int result;
for(int i=0;i<st.length();i++)
{
  if(st.charAt(i)=='+')
  {
    result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
    System.out.print(result);
  }         
}

dan melakukan hal yang sama untuk setiap operator matematika lainnya sesuai ..


9
Anda harus membaca tentang cara menulis parser ekspresi matematika yang efisien. Ada metodologi ilmu komputer untuk itu. Lihatlah ANTLR, misalnya. Jika Anda berpikir dengan baik tentang apa yang Anda tulis, Anda akan melihat bahwa hal-hal seperti (a + b / -c) * (e / f) tidak akan berfungsi dengan ide Anda atau kodenya akan menjadi super duper kotor dan tidak efisien.
Daniel Nuriyev


2

Namun pilihan lain: https://github.com/stefanhaustein/expressionparser

Saya telah menerapkan ini untuk memiliki opsi yang sederhana namun fleksibel untuk mengizinkan keduanya:

TreeBuilder yang ditautkan di atas adalah bagian dari paket demo CAS yang melakukan derivasi simbolik. Ada juga contoh interpreter BASIC dan saya sudah mulai membangun juru bahasa TypeScript yang menggunakannya.


2

Kelas Java yang dapat mengevaluasi ekspresi matematika:

package test;

public class Calculator {

    public static Double calculate(String expression){
        if (expression == null || expression.length() == 0) {
            return null;
        }
        return calc(expression.replace(" ", ""));
    }
    public static Double calc(String expression) {

        if (expression.startsWith("(") && expression.endsWith(")")) {
            return calc(expression.substring(1, expression.length() - 1));
        }
        String[] containerArr = new String[]{expression};
        double leftVal = getNextOperand(containerArr);
        expression = containerArr[0];
        if (expression.length() == 0) {
            return leftVal;
        }
        char operator = expression.charAt(0);
        expression = expression.substring(1);

        while (operator == '*' || operator == '/') {
            containerArr[0] = expression;
            double rightVal = getNextOperand(containerArr);
            expression = containerArr[0];
            if (operator == '*') {
                leftVal = leftVal * rightVal;
            } else {
                leftVal = leftVal / rightVal;
            }
            if (expression.length() > 0) {
                operator = expression.charAt(0);
                expression = expression.substring(1);
            } else {
                return leftVal;
            }
        }
        if (operator == '+') {
            return leftVal + calc(expression);
        } else {
            return leftVal - calc(expression);
        }

    }

    private static double getNextOperand(String[] exp){
        double res;
        if (exp[0].startsWith("(")) {
            int open = 1;
            int i = 1;
            while (open != 0) {
                if (exp[0].charAt(i) == '(') {
                    open++;
                } else if (exp[0].charAt(i) == ')') {
                    open--;
                }
                i++;
            }
            res = calc(exp[0].substring(1, i - 1));
            exp[0] = exp[0].substring(i);
        } else {
            int i = 1;
            if (exp[0].charAt(0) == '-') {
                i++;
            }
            while (exp[0].length() > i && isNumber((int) exp[0].charAt(i))) {
                i++;
            }
            res = Double.parseDouble(exp[0].substring(0, i));
            exp[0] = exp[0].substring(i);
        }
        return res;
    }


    private static boolean isNumber(int c) {
        int zero = (int) '0';
        int nine = (int) '9';
        return (c >= zero && c <= nine) || c =='.';
    }

    public static void main(String[] args) {
        System.out.println(calculate("(((( -6 )))) * 9 * -1"));
        System.out.println(calc("(-5.2+-5*-5*((5/4+2)))"));

    }

}

2
Tidak menangani prioritas operator dengan benar. Ada cara standar untuk melakukan ini, dan ini bukan salah satunya.
Marquis of Lorne

EJP, bisa tolong tunjukkan di mana ada masalah dengan prioritas operator? Saya sepenuhnya setuju pada kenyataan bahwa itu bukan cara standar untuk melakukannya. cara standar sudah disebutkan di posting sebelumnya, idenya adalah untuk menunjukkan cara lain untuk melakukannya.
Efi G

2

Pustaka eksternal seperti RHINO atau NASHORN dapat digunakan untuk menjalankan javascript. Dan javascript dapat mengevaluasi formula sederhana tanpa mem-paring string. Tidak ada dampak kinerja juga jika kode ditulis dengan baik. Di bawah ini adalah contoh dengan RHINO -

public class RhinoApp {
    private String simpleAdd = "(12+13+2-2)*2+(12+13+2-2)*2";

public void runJavaScript() {
    Context jsCx = Context.enter();
    Context.getCurrentContext().setOptimizationLevel(-1);
    ScriptableObject scope = jsCx.initStandardObjects();
    Object result = jsCx.evaluateString(scope, simpleAdd , "formula", 0, null);
    Context.exit();
    System.out.println(result);
}

2
import java.util.*;

public class check { 
   int ans;
   String str="7 + 5";
   StringTokenizer st=new StringTokenizer(str);

   int v1=Integer.parseInt(st.nextToken());
   String op=st.nextToken();
   int v2=Integer.parseInt(st.nextToken());

   if(op.equals("+")) { ans= v1 + v2; }
   if(op.equals("-")) { ans= v1 - v2; }
   //.........
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.