Java簡單公式計算器
最近給公司開發業務代碼時,碰到一個場景,簡單描述是這樣的:
客戶要向咱們公司定制一件產品,這個產品呢,有很多屬性,那公司得根據這些屬性報價呀,怎麽報價呢?公司針對某種類型的產品有一個基準價,在同類產品下,某個屬性超標了,需要加價,但每一個屬性的加價方式都不一樣,針對每一家客戶加多少價也不一樣,每個時間點加價比率也可能不一樣,真實情況要比這個復雜不少,這裏就不再深入討論。
那麽應對這種需求,我首先想到的關鍵點是:要把加價這個公式,暴露給實際能控制它的人員去輸入,把公式中需要用到的一些參數,以替代符(或者說變量)的方式提供給他們,比如,用a表示基準價,b表示屬性超出數值,然後超出部分需要乘以該屬性的單價5塊錢,那麽最終的值就可以寫成公式: a + b * 5 ;實際運算的時候,假設a是100,b是20,把他們代替 a和b,公式就成了 100 + 20 * 5,看起來很簡單的公式,口算都能算出來,但是正常來講,公式錄入系統,是以字符串的形式保存的,一直到你把真實的值替換到公式裏,也是字符串操作,計算機要如何把你這字符串的裏的內容正確的計算出來呢?
OK,其實對代碼邏輯不是非常好的同學,可以用一些簡單的方法,比如,將最終的公式用JavaScript的eval()函數執行一下,就可以得到結果了,這個方法也可以用來在前端驗證公式錄入正確與否,還有一種方法,把最終的公式直接拼接到SQL語句,對上臨時表查一下,如:SELECT 100+20*5 AS result from dual;也可以得到結果,用這兩種方法,其實還可能進行更復雜的計算,充分利用JavaScript和sql提供的函數庫。
嗯,回到原點,我們現在呢,要在Java服務端實現字符串的公式正確計算,公式雖然簡單,但可能每一次要進行幾百條公式的計算,也沒必要查詢幾百次數據庫,而且這種和金錢相關聯的值,如非必要,還是不要拋給前端來替你計算。雖然,公司這邊並沒有采用我的方案,但是我個人還是把一個簡單的公式計算器寫了出來,留個思路,以作備用。
簡單公式計算器能夠滿足 加減乘除 和 小括號的運算。
我個人非常建議新手練習一下,基礎運用得越紮實,對以後的技術瓶頸突破越好。
代碼圖上的註釋比較少:
粘貼處代碼圖:
代碼來了:
package com.supalle.test; import java.math.BigDecimal; import java.math.MathContext; /** * @作者: Supalle * @時間: 2019/3/8 * @描述: 簡單公式計算器 */ public class Calc { privatechar[] val; private int len; private int inx; // 構造器,把公式傳進去,比如: 100 + 20 * 5 + (1 + 2) public Calc(String val) { this.val = val.toCharArray(); len = this.val.length; inx = 0; } // 獲取計算結果,使用方法其實就是 new Calc("100 + 20 * 5 + (1 + 2)").getResult();就可以得到結果了 public BigDecimal getResult() { return nextValue(BigDecimal.ZERO, ‘+‘); }
// OK,接下來的兩個方法,必須要弄明白,下一個值和下一個參數的區別
// 為什麽要獲取下一個值,加法、減法、和左小闊號,都需要獲取下一個值,因為加法、減法如果碰到乘法、除法,那麽運算優先權在右側,如果碰到左側小括號,優先權也在右側,所以要先把右邊的值算出來
// 為什麽要獲取下一個參數,乘法、除法,他們下一個運算符如果不是左側小括號,那麽應該從左往右順序計算,因此需要直接取到下一個參數進行計算
// 還有一點要值得註意,那就是:在運算時,減法一律替換成加上一個負數,以此來消除實際對一個負數進行運算產生異常,比如 1 * -3,總不能檢測到 - 的時候,又去做減法運算吧
// 就講這麽多了,不能理解的同學,再反復推敲幾遍
// 獲取下一個值,傳入第一個參數和第一個參數後的運算符 private BigDecimal nextValue(BigDecimal param1, char operator) { if (inx < len) { if (operator == ‘)‘) { return param1; } if (operator == ‘+‘) { return param1.add(nextValue(nextParam(), inx < len ? val[inx++] : ‘)‘)); } else if (operator == ‘*‘) { return nextValue(param1.multiply(nextParam(), MathContext.DECIMAL128), inx < len ? val[inx++] : ‘)‘); } else if (operator == ‘/‘) { return nextValue(param1.divide(nextParam(), MathContext.DECIMAL128), inx < len ? val[inx++] : ‘)‘); } } return param1; } // 獲取下一個參數 private BigDecimal nextParam() { char[] param = new char[len - inx + 1]; int paramInx = 0; while (inx < len) { if (val[inx] == ‘-‘) { if (paramInx == 0) { param[paramInx++] = val[inx]; param[paramInx++] = ‘0‘; } else { val[--inx] = ‘+‘; break; } } else if (val[inx] == ‘.‘ || ((int) val[inx] >= 48 && (int) val[inx] <= 57)) {// 如果是 . 或 0 ~ 9 param[paramInx++] = val[inx]; } else if (val[inx] == ‘(‘) { inx++; return nextValue(BigDecimal.ZERO, ‘+‘); } else if (((int) val[inx] >= 41 && (int) val[inx] <= 43) || (int) val[inx] == 47) { break; } inx++; } return paramInx > 0 ? new BigDecimal(param, 0, paramInx) : BigDecimal.ZERO; } }
Java簡單公式計算器