From e07d24431d9158953a182b033d28e4e01562bf1c Mon Sep 17 00:00:00 2001 From: jaellysbales Date: Thu, 21 May 2015 16:38:35 -0400 Subject: [PATCH 1/2] Added finalized calculator --- .gitignore | 6 + ScientificCalculator/.gitignore | 6 + ScientificCalculator/.idea/.name | 1 + ScientificCalculator/.idea/compiler.xml | 23 + .../.idea/copyright/profiles_settings.xml | 3 + ScientificCalculator/.idea/encodings.xml | 5 + ScientificCalculator/.idea/gradle.xml | 19 + ScientificCalculator/.idea/misc.xml | 10 + ScientificCalculator/.idea/modules.xml | 10 + .../.idea/scopes/scope_settings.xml | 5 + ScientificCalculator/.idea/vcs.xml | 7 + ScientificCalculator/ScientificCalculator.iml | 19 + ScientificCalculator/app/.gitignore | 1 + ScientificCalculator/app/app.iml | 93 ++ ScientificCalculator/app/build.gradle | 25 + ScientificCalculator/app/proguard-rules.pro | 17 + .../scientificcalculator/ApplicationTest.java | 13 + .../app/src/main/AndroidManifest.xml | 21 + .../app/src/main/ic_launcher-web.png | Bin 0 -> 39609 bytes .../scientificcalculator/Expression.java | 1100 +++++++++++++++++ .../scientificcalculator/MainActivity.java | 898 ++++++++++++++ .../sufeiiz/scientificcalculator/Numbers.java | 64 + .../app/src/main/res/drawable/box.xml | 5 + .../src/main/res/drawable/button_bg_color.xml | 5 + .../main/res/drawable/button_text_color.xml | 7 + .../main/res/layout-land/activity_main.xml | 462 +++++++ .../app/src/main/res/layout/activity_main.xml | 316 +++++ .../app/src/main/res/menu/menu_main.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3239 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1779 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4720 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 8968 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 14269 bytes .../app/src/main/res/values-w820dp/dimens.xml | 6 + .../app/src/main/res/values/colors.xml | 11 + .../app/src/main/res/values/dimens.xml | 5 + .../app/src/main/res/values/strings.xml | 29 + .../app/src/main/res/values/styles.xml | 35 + ScientificCalculator/build.gradle | 19 + ScientificCalculator/gradle.properties | 18 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + ScientificCalculator/gradlew | 164 +++ ScientificCalculator/gradlew.bat | 90 ++ ScientificCalculator/settings.gradle | 1 + 45 files changed, 3531 insertions(+) create mode 100644 .gitignore create mode 100644 ScientificCalculator/.gitignore create mode 100644 ScientificCalculator/.idea/.name create mode 100644 ScientificCalculator/.idea/compiler.xml create mode 100644 ScientificCalculator/.idea/copyright/profiles_settings.xml create mode 100644 ScientificCalculator/.idea/encodings.xml create mode 100644 ScientificCalculator/.idea/gradle.xml create mode 100644 ScientificCalculator/.idea/misc.xml create mode 100644 ScientificCalculator/.idea/modules.xml create mode 100644 ScientificCalculator/.idea/scopes/scope_settings.xml create mode 100644 ScientificCalculator/.idea/vcs.xml create mode 100644 ScientificCalculator/ScientificCalculator.iml create mode 100644 ScientificCalculator/app/.gitignore create mode 100644 ScientificCalculator/app/app.iml create mode 100644 ScientificCalculator/app/build.gradle create mode 100644 ScientificCalculator/app/proguard-rules.pro create mode 100644 ScientificCalculator/app/src/androidTest/java/nyc/c4q/sufeiiz/scientificcalculator/ApplicationTest.java create mode 100644 ScientificCalculator/app/src/main/AndroidManifest.xml create mode 100644 ScientificCalculator/app/src/main/ic_launcher-web.png create mode 100644 ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Expression.java create mode 100644 ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/MainActivity.java create mode 100644 ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Numbers.java create mode 100644 ScientificCalculator/app/src/main/res/drawable/box.xml create mode 100644 ScientificCalculator/app/src/main/res/drawable/button_bg_color.xml create mode 100644 ScientificCalculator/app/src/main/res/drawable/button_text_color.xml create mode 100644 ScientificCalculator/app/src/main/res/layout-land/activity_main.xml create mode 100644 ScientificCalculator/app/src/main/res/layout/activity_main.xml create mode 100644 ScientificCalculator/app/src/main/res/menu/menu_main.xml create mode 100644 ScientificCalculator/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 ScientificCalculator/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 ScientificCalculator/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 ScientificCalculator/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 ScientificCalculator/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 ScientificCalculator/app/src/main/res/values-w820dp/dimens.xml create mode 100644 ScientificCalculator/app/src/main/res/values/colors.xml create mode 100644 ScientificCalculator/app/src/main/res/values/dimens.xml create mode 100644 ScientificCalculator/app/src/main/res/values/strings.xml create mode 100644 ScientificCalculator/app/src/main/res/values/styles.xml create mode 100644 ScientificCalculator/build.gradle create mode 100644 ScientificCalculator/gradle.properties create mode 100644 ScientificCalculator/gradle/wrapper/gradle-wrapper.jar create mode 100644 ScientificCalculator/gradle/wrapper/gradle-wrapper.properties create mode 100755 ScientificCalculator/gradlew create mode 100644 ScientificCalculator/gradlew.bat create mode 100644 ScientificCalculator/settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a881469 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures diff --git a/ScientificCalculator/.gitignore b/ScientificCalculator/.gitignore new file mode 100644 index 0000000..afbdab3 --- /dev/null +++ b/ScientificCalculator/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/ScientificCalculator/.idea/.name b/ScientificCalculator/.idea/.name new file mode 100644 index 0000000..883c721 --- /dev/null +++ b/ScientificCalculator/.idea/.name @@ -0,0 +1 @@ +ScientificCalculator \ No newline at end of file diff --git a/ScientificCalculator/.idea/compiler.xml b/ScientificCalculator/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/ScientificCalculator/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/ScientificCalculator/.idea/copyright/profiles_settings.xml b/ScientificCalculator/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/ScientificCalculator/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ScientificCalculator/.idea/encodings.xml b/ScientificCalculator/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/ScientificCalculator/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/ScientificCalculator/.idea/gradle.xml b/ScientificCalculator/.idea/gradle.xml new file mode 100644 index 0000000..fe865d3 --- /dev/null +++ b/ScientificCalculator/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/ScientificCalculator/.idea/misc.xml b/ScientificCalculator/.idea/misc.xml new file mode 100644 index 0000000..59436c9 --- /dev/null +++ b/ScientificCalculator/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ScientificCalculator/.idea/modules.xml b/ScientificCalculator/.idea/modules.xml new file mode 100644 index 0000000..e35acaf --- /dev/null +++ b/ScientificCalculator/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ScientificCalculator/.idea/scopes/scope_settings.xml b/ScientificCalculator/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/ScientificCalculator/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/ScientificCalculator/.idea/vcs.xml b/ScientificCalculator/.idea/vcs.xml new file mode 100644 index 0000000..def6a6a --- /dev/null +++ b/ScientificCalculator/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ScientificCalculator/ScientificCalculator.iml b/ScientificCalculator/ScientificCalculator.iml new file mode 100644 index 0000000..0bb6048 --- /dev/null +++ b/ScientificCalculator/ScientificCalculator.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/ScientificCalculator/app/.gitignore b/ScientificCalculator/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/ScientificCalculator/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/ScientificCalculator/app/app.iml b/ScientificCalculator/app/app.iml new file mode 100644 index 0000000..078bbeb --- /dev/null +++ b/ScientificCalculator/app/app.iml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ScientificCalculator/app/build.gradle b/ScientificCalculator/app/build.gradle new file mode 100644 index 0000000..32762ff --- /dev/null +++ b/ScientificCalculator/app/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.1" + + defaultConfig { + applicationId "nyc.c4q.sufeiiz.scientificcalculator" + minSdkVersion 15 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:22.1.1' +} diff --git a/ScientificCalculator/app/proguard-rules.pro b/ScientificCalculator/app/proguard-rules.pro new file mode 100644 index 0000000..39ee882 --- /dev/null +++ b/ScientificCalculator/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/sufeizhao/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/ScientificCalculator/app/src/androidTest/java/nyc/c4q/sufeiiz/scientificcalculator/ApplicationTest.java b/ScientificCalculator/app/src/androidTest/java/nyc/c4q/sufeiiz/scientificcalculator/ApplicationTest.java new file mode 100644 index 0000000..2380304 --- /dev/null +++ b/ScientificCalculator/app/src/androidTest/java/nyc/c4q/sufeiiz/scientificcalculator/ApplicationTest.java @@ -0,0 +1,13 @@ +package nyc.c4q.sufeiiz.scientificcalculator; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/ScientificCalculator/app/src/main/AndroidManifest.xml b/ScientificCalculator/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..08814a1 --- /dev/null +++ b/ScientificCalculator/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/ScientificCalculator/app/src/main/ic_launcher-web.png b/ScientificCalculator/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000000000000000000000000000000000000..25d627637f3f4275ec6b6f98e78c83a1326e5f01 GIT binary patch literal 39609 zcmeFYcQl-D*ET$(cZn86nIw7%fkA3Xp41J?2M@&FV0001p735!Q000>1 zM+^WS4*J)r$J7-700AhxmVWCozu$_VPN&s~^1DT#4s*2CL6cxr70^>W>bRYfOsvd**Rqn0r^-lkJ;!Xt7r* zv`1NkU;GjOz=@9pOl@YTcDF@s+B3-y`64Wb?f?Jz|Fi=odS!l{SGVQE=_m8EDt5nK zK+d9fE_rSwH4#}?j2HZsmh-pkg|g}tGU`Jy!1vzvH<93KmNFvR!uD0amzWUAz%lAK z=npAmFs1JPXi9^0pR7pRq5ZHHM5DiPrZ8VkmOS%JU928u&+K%@!|7CdUM!Sx$~X2g z1uDRg6uwt@{%WL=vp0M7-Jz|;*)*l^#NF!zqk=l3IlXvIm)CWTnk}_rYI3b38!d2| z2ZpT`(`QJcaJ|lPR4jet8@6AMxw>UBmD=IZ9?av$)h$V_vZ~xIMF8;6x?1OThssW2 zn$bq9g4uqR?l!UW2n_s9MwJOGIZ&q$f~nd%v|8Jh*OJxxhA6ta-NEAg@ydv_1qrm6 z@v&rm$b`H%k}{dN1?N`e9K9vivUef2t99@E!l{<@s@m!y{&ffte=|^l%3#B3d<4FC zn;?WF&1d&BWP9o*t=#d1!SX1fui^cDO-D#9(acP;(+4{l0C|T)&|R=v0se{DU0LK& z`|WL^l_x{;s+#4&Qk;fS^v7;}rrxn5k$PDu$_hiY?@T|{g3 zfIcoN@9|01c8V0XZukL50Cz_bDSe#v%yRHnvw-weS&QjC^egDr`(E>Q8}HbQqhKQ&oh3tt>j))vEPLr4&g6i$wf@n5YQ771E7ZdKbSg4JN`CRdvMv82TaPzD%HT2ZioMsU?17zsuk!HG zp}kZeQ3qDPspM~`bC%0yMoy>s>aZ^L?*I6LL-hqs30_xK^F(7-DbeyEalK-Sh!Z_H z&xZFY*>* zRShbu;Jn?U2OI=lj%cF)$x!Vzo00Ycg@FEzl7hvdMhUhl*2)J-iE7eNC!x|j13k5! zq2ZrDO4}KkTx`>R2A;o?Vpwy;AB$dhO|nW(yN`@`T(RoIY`tK2cA0i!6ZiM60)W2} zUAs^C%GDKSgz>wXdCm{FCq-O0U^{p4w|)DAMxYu&J>|6wgQ-J)yZLf~z*3$&(}BXI zl-R7QgV5w`KQ0kiu^w|>I74hRW3b2>^V;!S`drE$rKevX9`44}3!W^-OW=d=w?_og zBY1cUKhkbz$DHg}=heCHB3Y4<1o~SMP z?fO}Tukwz4R`}xN?X1agL}X6ng^Oj;vCJvELISGrt7}2ZpykUp_VOXe;jk9Ia9-?#x%S}AB z1s~~p_5xDnOtLl$20`>`O$8<4=z;vb7K8-*0t&*PvWf0rav~Kw&lvDav_17*KRgMq z12|d09!Ri8d?vFzYbCez#iAYcY5ID8j)}FjYFJGjTfxZ-G@W@jhajnpexT31X$<%x zEL4L=o06{F&v*F%%m!xPWjZb3Z(`6Ea)ewDccHuv$bXdj-KS9Sdt!4EP75xZO@b^^ ztsUEeI$brH;K2Pf*1*yh!_W-+w_#NhlJD6ViOkA@WDtJO2q-3g7bC+AQNuR<2mKki z*|?KuTfoYG;1W$hWwd|GbIYfD#*)RA-v71F{|+L-s43;WAF+8X2>@@27=V}G#!QnU zRR*V@dsU%bTTFu&MrBC!mFGYB+bxr&C9tw3&1mjJ_Az`_9G(o_e2oajZ>U<)=Ob+9 zv~AS#rl2Np<;7xSj?i-x={Hh3RZ_FK9h)}mTqKGoC+fx0FX`X=)rU~N_uH*ZC-G^O zehhk3XdQN4Ea;h4M|8SAW!pe+9u4@9qF!RGbKR@YjkZ>iEWo!3%(CVt4+geqJbAuF>@fqIc8;l?2z- zDu|}yT<5iOiD(u-SH@ew7(c;|9 zgnleK-{aU4Arcgavdavt^$MT&IW@QGb@tsMEW|!Ui~x<1McM}PJ#nQQ_3X~IUb>;- znMD%n6#{?8=RU5b;vVQ?%6xBmEF*`>u`WY-oQTegVUnt551nhM7>%Ty|nf za#Nme1dK8S8}B)7b~hbB77jPuI?~xIPAUqs=`_|u?a!G5g`traRiKgwVjt|+YYj=( z&(gUnwidEi_mf=HFpjtL(4n80>~qZX^Oq`PvVcr^59zyr*0U|n+RG8lMU^nD`U48( zJaOWR1c?VAI*o*!p;zFD4Z zmGbo~xxNCgaFP@7Po!(7Mf?;asKcf3l!PT9yf9+=P)d#TF*0gV1>VNT^9(#950^8y za3~fiKnFT9(wI*KZ3t;8?+Rmy+cb|fmOrC$g|L+la-=fOU>c-{)yYa8;w*_@f^EWS z!bEM;*WNQ-ud3k^(I?YR8u3%<$wAwT)O9iZ8fe%b52Aik{YU9|>(G$5zXH{&mPeSs zFk-A*o$ z>G~ywLOEZaImHN_yw#q();}#0`cld0#o)+M2|4@Fpx<3hxKCawQr*c~$` zc_n<@UoR>vyooKL&dcxpeEpIZB^=^AdY7eW68X0r{0pY0oLQ$5#^iW8*>#!5C zHjFX*`#t7xKqKJ?eG=SWL(~m3rS9ct3zip5o)Upj>;8Ra`r%C7EEw!Lar_2wzfbM1 zki?*AN1Q9Xi8$HzM*H-bK{@qVXJHA}YaAZ6wZmE9QRm1`KhMclJqeJJ$%( zW)$(@X9k80>WGG!(vqF4pwQos$M@RF;*d*7iNTLlZNWa=Oet7S{6F@Tn}27FC+vam+M6nwNV-0V%{ zc-}Q*8`{%cw~aqzlwLu6Nf}>Q+&2Eg?j>aiy;86t(FmYwp3VKPj=7x!C+S)^TxDqC zc`&@6B{qtZ$2iiK&b5=sc@;Ir$J>IPRntpNNs%?Hkg2XJ!X^6jL+MXG5L8_ZVCvl73C8@ptZMiE$Epm0S`yYI-SN}lKf zcR(x=A&SdSf>~}@MKl%v3fH@k1YrP@du%V)47}ueN;Blv2dB*hzzr6e^O&VcaM~Vb z-baV%U10u0!=|e8j_V`~knEl_?J1b4qetCaaVYpOVpVCK0Dge6PKZZ#8k@L8v@#=l z-X>0#e)BGnEPjeo3x~2`o${4ikgScH`WySgX>48rLhmDANHjIJjA3_MnAo`|e4Yo< zuK z@D`mnd>vGSjnl~^Fl96R+K!@~B{M+DMCI@+IDt=HEqo4{gr(r4^%EayCh3`>XtW?e zQNulB9iSx{?U-sf@eCHK=`9dr?4yvLDkrrp3WDiX2*BS*DqjAY3*qg(NAV(h0mQ_FFpH_0J;omS46C}uSfSH|!H-Im#=?S~nO3{%)(bubzk?{MiKWJ>Mtvh%Zh zgLySEUl)cI#2G;H>B$^|M?QCr=@?vZe8Y60gFh_vqb}!gWUa0lt1F9x5G&V@h_MD< zCCUegzED07uy|)=(0Oq4l@@{xzh5`0chV1NDd>+{e2FXGM4-+*r~P}jEi zj&H0p@#>sBsL<)J@){GQePgbW3gOgX<_XkO-V#5Em|fi`Y4aJIJzX1YZ`n;M)v39} zo-3WzXfLTnH45{RQe5k}5O%~~rGL=&bDwx73h>j;W}{TraM7cdDy@@wdIYkH^aT!L zQ|@=#js*)pbe=8f|JC8n63t)wDoG*0L@jU_b}$CLe0!>OoM)lr8j8j1Bo_WJnEwm( zcZ~BN&S|rVXYQ05^Pqm;6<;ibJr|FfXFG;b%|`N@aYX~sRdrd0#27!Nyd%7 z!#joU_1}t$bLE!4FL!IjT7*G_1aYmJ=i8*Kb$@9cYjG&Nx=c${$@%^xWyX}*Y11;y zr>@@B3)f221QvY6IE_PS@Z!qhr3!bm3i!v1CA}mC!T5fRr6^*_m8*nrc~l1)^d? z3Dv90Ils+?X@wkyJLr8HHT@htqK`=!bk@#-P7dCTh`pbF977c>A=)q&%Uy_51uf5 zfr_&>Utvc zeZ4W~MjKb^x9sXHtZhaNKCG=FWCMMN6=mm5z6|j@-Qzm%#)H^2NRIwwIz=5>B6Ob3 zcWh#XqXL(IVj1OYDM7R@+-~4=WVz9uDw|$-FOjc}BGGBkR!DTE)S1W!=QLN`MO}nC zQ9uKYx=K>3%ax=kvKW7?TnFTi?`2T?f~@0op?0M_hD+uY3h?Ph@#1>>5f9KtTa7B? zp>m~n`EQ{mQ4u@qk<6>BKZae1=Z2EE=-#pj|LX^ws3gRRHYn>?Sw>>tv==Uy2d=F0 z*c!uS1iII=Li2hhRln|AuKiCYL_etOk0{gU&OBCm9e@R8Qp+dVbsqLLBXXzI{`f1u z6wm>DM_K+MEs^<--uey+YjvL8zj%N~P17lGgk!CYgxBNtGagWKp_>t?(~h;Zmat22 zY|cFqwY~Z+$LGqKz?3Gs(x0>L6(2D@#$SBHjpV z^$gI4sJE;%jj74?C?z?f70s*9%q-U0+@K|ZK78m~bT`8Hjyh(;<|Behu+S^LRbOd> zDk0{1UT;OX{Rko)+cLUqnp=w7=OUeKe@1hiZ&WVDSe@g-wfA|u+9yS#5yQ|@2V&kO zwTg>AmoY|F`S-7HB&_O&Eqm~YJT$yN`@D$eT#7nL=s+fsS++lh-B1BojK5Xv)htuS zx*f5ECD-9%pq4k5{I_ZsC+k&5hZ&vs$Kmd2UUQ;%I@13X1KRwq*vik<4hb^X^imkN z(p{bmmJV#yLl3lKS%BI6-OGlLSXPpxPzPucCd5L_gvw{JDobO1i-)_O7+xS5E=UXa zvezEtdxNna0j-VNW6O@0U&<$dfKVN1E_ zHtQ@+#Gg9z?7mcpthlT(fHv?vL(y|+8CHi_5RZP)B9y8htojMRO)XGkbf^blmQhA< zvg5d5^?2P4-FQW+Vcx6hJ)7LS)~flXM77!;(Q;D?9&?X8c`a$zw0s~fHr8xt{p>E> z1h%GFILXF*3olgN%p2!;fy21H5*8cQ4Gw0UId2eEaQe=Uc%VmJZG)(0Y^rE{ z3w`A#&fAY0qOPg1SMX#6(DS<|dr{8JS{ya6N)3)?k+Z+T?#!ihp#9&#u>dE}OXo1)JaY3_JOsWXdMVa^o|}L4320 zwTV*hl-;JS=q7u7UR=&xUK+Il=*_!v4arI_?3M-WLN~o zTt}z{mwA*DyS!Aekhr=3^Re6JtRnf(AAm{Xvrm`)FHcxzr4}<6DV|i;vQ~Rj9OOBa zF2D0y4Ga!jeTm@Y4a!&VT<>-RlaEQxi6(HORn(zYV8`6jXMV6^tkD@KG~yzRtG19c z`EXWpOq4R)O+RbF=v$!$C#0wBYZSSxCdkvyok~A>ws)(Nq~!^g9yQNOAG;_rm+qug zNHE&N=OmjzoSuqrRGR@QB7<>zKIpjSQ#g|H44(yXr$KD!hx*jLu6M^C9EkUAakQv7 z6+(hx{8AlUnB{0a{BO1Wr`~cicG-E*!vn=H^32=bX^>prrxJFR6CJ*+`aTNHb0fVI z5I=X2UOO^!pV`hzviP@SqqCpr!GYo{)3MM>YBHwiKL!UexGY^US=vggyoF}jb+TFf zdw@&(x`dUBYC2IK)dR5Ndz^-%iJ_H5q!QeZQLF0!01M@-&VtwMyNXj)GLn)5t`ReJ zS#P>JU*|r{&Y+rpd72FSN!A0pgO1)Ec-LUNNkA2zpGxMQ1NMt(*AH}?_yQW$gi=P=mFH=& z!{6BaAnjWt!1f%n5zMo(Q@;?OxxDmHdrp?x_#t#jV0y1W#X%(+0C4+ z1hez6_T*U8M8>B-T4r$}S*$Dj2=Rj}`&9u&2KxWnQWYN)HT~-2IR9m@CkF?(3h!Y0 z0T+x)0eVH<4*`DOg6-x<`+-&OvvRiAFsK2n zwo+qP%}%L_sx+W7aSeI^^Y%i8$8aucd>1m8Ldse!T)-4=zq2BW|Fn*P={j@$Pv*Nf zvK-eq#*XS}sqU<1KOjR%wd)7>J-vF|(NoW1ZcIosdF80i;!%rr z=D@Z(EmVGbI;coBUoqGY#rD)&?Tscszb*HkaGtJs;<&-*CQGQ#tDi%or80-GugggZ zOB}hJXq;M3O2Q`y@5j|~Z7ets=AE@t!w{y5l8S7rn1t@ncP|qkuLX}3`d=>!(@*}| zNH+0P{#AHlrV&q+YQ)Z4G8kYNh1SmQ9g@X5CV*19l=CaIZdWwCzS$61n{fKl**M*m7|G);H zV)nu0GK$0Bbk;&Q8b~(q{;=MR!i_8*aKU_ec+PVH)_JM&r{Z6p{LxL3w_Ghh5a(8(Ff|BBqF@k-i27E7w0L{2)lJ%Fz z{KI7a_fe390!Mag*_4qyd$~Do->iZ`=H+apxdPREN!`9EP^2XoTCw5D@W5bkhwKaR znbz)22$9RL)~_=Xt6vekpKgn9fpeRPY3<*;3TURjm5doTrJwqwPvb|H#Nf?^_M2QI z4JI#iJ0O8&$g6(@N#Q0}SV7qf?rLcW&0%@xqlKK;ml>*yF6YHz$XrsT<2KU%tSt}-nVl}f$QFV>vTN{Y z7#I@_&m(D5>-Zo#Kg?ha+ltUvTg=ZeIcw*%B@)j>%KcMA%bz5j(((orQ+;}i% z5XU6;gOAML`yPJbcd2uHW@ToPRKWkxzwJBX;HoKx6F{DA3{c0dDI7&=aoLYe{no`N zJW2E1Qhd}zVHYJnN>^+^35=#dE1{`fOiz^&lbKs>m#T#<$b1_dfIn>6(6L6Xjm&YQ z0vghY;l$|`{w%M+i(+yyAg}s{Pvs4a@QUDgfu1pSzRFv!#)&rg5eXf4+?H`wx0R)g7aD0=rkq;8I z?yz&RVE}pp%I8p?+ja=tn~vgPltM%Yd*(31gjHHJB+u@}7}cz2+Mc86pl{WVS3NG= zn>E@+rgO=_4p#{IHfF&&uq9Ku^E^3#YEE~ee=P}3ULTzId>Uko>2*`W249JmxpE?$ z{i?201Vl(ap+Ih}M>C~P`+a)>X-BxEDJ%$$kl^~z5n_4EuSr>=TKnOZSxU=Xa?z7Jd8_lzYvJwITzrJ1(p}(`~22EZl-WJFAeYAlsyuQq26~VX!%0zX*WHmYFgnxqVJ93#* zGSJDY*H@169h0}U-8EBgd3rY``_#Jqfp2HXhCjaLl)X@KN7L{`zs?G*PPGw-F(dbw zp~JUA;zjut60Ow$$g$=0aiheihL=e`JTMqbN{OWOtsaV6pL@^Aiy0D%e?c#G1fVAu zOI-${?5PxWPFgRJUC(-cW`hf#>1QXbUC}qV0M-Ssvlo_uuNJgR75>O>%sN>t0E7jo zyNOfL15r;`G`ap?l+sf1ZLjT*Lex_l*lG5{G1 zcwV7v44IxJP&8Q^PREWRA9LNz1>Gvc?aFrw&&oTV1m_eDzJwb*36EesI7sR($d7pZ zZAVA4SWO|9ltaE`!Z-ro2kd-?G4}3#=AMa~1>-{*h3TPNR_+C@U-jjngJS-Y(6Esj)i0?TVH zdUzb`z6|O55-^vO{J}wq)h`OX=A_H@eCZQ)wPjuE+NJt$4|VG&XS>8#g!n@^N8ALe zVP|b&>%f`Lt1NwXG!eUQYuB1T~gv|9J9RG|T8Mgk5iJ@boBlzoG zZOADy*EI>Lz0-n(-NmZCT|aNlJ?sWg$|R~eWI_TCMkz| z9aPR74U3Ej%wuPhLXL5n_wnD{JttEGd1Srr6G6TyWJe-Tw@c8|VD_>H{rDMN8v%)3aoBTb|1SM?vq26edE`>W91~6#aoSTXIM{25ySlbXrJ@ zr^50(ipjAW9ucxXB?f1?2PG`am!Vd2KrX9QtlCvWLS2%*44?o`*yOe;$XZ5Jenm1j z&+1r1`4TDT9;r2)Wv+g0*EzcZZYS+1${rwu;IDkG+Q(BU3!_t$WM>74xU?oi3dM@S zCU4h#MEf@Aoj1U3xGb-roF)yk!N!ZOo0FN>Q61I2Usku8MSu3@2_b79S8Ta5I~b)$ z8<0G7`X5E)EOB<}v^GsiQ8@QA6 zIo?<)dw+s8T)pwdk#g1xAU0gglKqnRv8ZI$9z!c{7nFSQG9ii~=67Q_VF?OI!@*0UvU* z1x~Z4QA4I3&&#sg?VblcIy2sWHwShR5azvVs$`Clsb_>W@eK@+HN`E9MaO=CmexvtK4Up>dlI;-A{V0*({#>U;<(8tm4uW=&` zxX)jdvapf(VIzc;@8)-^^A_6oEYIxFo%|WwN9LA&Jp2lq{ogexK4C)y=A~A1>x(`y z3Y@VL?WK&%2j?_KxkjMqTViZ+WbK9@F$Vq*Grm)Ob>F>xD)dr9hWkoLKHaHmWr--}QTxS87VgMn|Z_fcX7W$`^^pL2aB#-nP=SdYW+>S&L+HazZ6MXYYhALj%6~-v- zK%qTS@2ZH!R6&LP#AvF&gFusQLS}reDszV zoLSr5`rI=DQxPul(BO=O&6_r_V4C{9r*}*qXT?x1?5?b(}7ZYrnbG3gxr)P2h4e|yjKFiw2bkg4MA zM*wR`+veA&vE$@q7i!vXrn3cby^c{;SeK?gl1YQ zl|W}qW9S#XGqBV|iQP}rl$^w`D~xq8JE7`U4P_?p@9)J7zc++jSSirb=Z0!3h#e+LN5Q1hO}CeJJ$RrkK(iUx6X@+oHE~d_B=(Y zY$4DTx#g-yt^qx$mdE~ZtX_9g*KkRq#lH*v9iB8$ofkahMykqC zB|nTWSvu)5dxT$9oq!VXf_i*pZaQDQdiZc>T@Z*vIIy=Yx#DFmu!Q-5joyb=7z4YI zDTVqGkg(qEeUIalkr3*=Ao<;C?KufiRkU(6wU6-uR-zl=F&da50ho)cn6J`SI$duV z+`qxr&Xtox8yRECHfLAFX5mvp{0jqrz?k=3oaJONk2O+Ol4V$5@Si+|sHR`fu>VhY za@U{yoxc2~*gp-%W&uI5k9F=vX??E2pf89L<|2i^@*jL8DPd_$*h|Vw6Y}ZUgZrwf z0E+HWXo||d^<&gGKR!Cxx1crHmBy9k-YLzjbV<$i zBXtg*@ActW>IdnqrE9zA2w}Q+mM`vf};il zQ*)q}dXSkya0TTQ01S-@;|A)Sk{p)>X(As+CwCskzBzGu^L6c1IsdB+qg6kpNC!O- z%pF-%SCAY??=mc{WkD0P7jU%qjkpf&bGSUv_{YwJHuT&@ga?sp6Es5Hb6;(9u43r1 zYlsH;Lo&FZ6x;dyvm=I)k#~X?Wz7}+Q5NC3jrw5zcFbeRBD`0@6IKr(>7R{%&kJsF z>se+osMteP*_ErdIWC(`DXE{C&ljUzkNmtd^yJ0b+Wn&V^-HPTCo~|770%?Rzf8=^ zf=(dm?iWKp+|h2&5epXRlT+f<>C;*PTy0;M{ar@f4;XFg)~Vl440y!ny2#qr2XeSC z4m75Ok{H)KuL&xE+r2#geh$$vOgblkw<3a$`HKNX9V2nz&g(2l2o|e~n~hg4#m#Ws zuj-9ir1O6F5;MV-+m9C<3e9z80ipu@s%2$($DVLD!{=D9W>d7))q1e#@`*-pw+~h0 z(zoP>E^oNUV9z5b0K=s!heIrOMC+IB`a2|2#sD%&weG`xr}MaL*?-3>TKHwN5o4o+ zPxe2~W%f+wN;DhS2X$W8YlU1BwT@6{5Uw9pGaW3w9#=Ny9VeD@QMIeG$2}E2S%udX zR>Kf1pe<5^gVjxQ15yr!h0&)pHCZ3gG8BU+@w~#=Ltzrrl!mPhKJnZ9j8n7`DiJL> z7GS+9VR$(0Sdue=CE)m-3bTQCQ3*QKPz!g(^1au5h3K|Pka-JiiOm~_NvF$h{nV); zHl_r~M6Q+k(l;+8Ozd_Hz#||ecSufwef+jzE_`eBfyywvFc%nHf6yTTZf+AhjSIJ2ES|iBV{LU zUX()D2#i_#BZi$DD0qZxo}a1~TeFweFTx9m{f;WNm_ho{f93;NpT10=FZ8FoEnMZ3 zG97D+gFXHiZoRp42t?w3TP3Akf=F0Cw`sUadWf3I$}m{AczyY%Rxg|ybMb6IXC9?yjm!qJqmF3Oe$-%*pW#Jd_pB%yO7^22in$D88haWWe=9v?m&*G7uWBu02rH0T)c!t#IYu9g zSLY*zc0rO-r6`U3*eywHk{D*KU}8O#l5ltTVA_cAI$-`8BfG-Ej^g;)(N4Nd{Ch2( z^@YvPd3I3jkzu-JIsvpe(r7`iFT)|W+t+T~FIdlXL~K0DzEgwe`yNgZqS5u=sCxHUq^)vu z;8j5;>XMCht<#})TQaffzHQd7#~fp@=QQq)CWUy}621xtqP98oc($QPQ6oUjt+#*6`t*$NU&~GFuU4X}{v2-qa9+^D9g*d+wekomFloT&)OWVBe z7(V`gT{?3KFEwj193Z&7J=`Y@wD# zeqQ{cK=swKV)xl$1yk9)p_A=Iy?FUn_edkzd+ez$Q$O--&Sj6QrvfiJws7;}X1R#{ z!zDPg)|REHR>@PXUGw{7UPN{Qu5+u8!a>;^DdXY_79E2QTIdj-=Cx2_W(-A zsC$YVHGR)nqQ3*tviIWJZfF}%iyXR!)W!#Kinb17CO<8ZUuhR^(D3lgD|>yDU~AJ3 ze1k0aFI(8;pWM<}p#I*9Ybq|%r=Y1Ku>UcU)M@{k&k^k$>!CmoKzGVMbD=R?y01X5#eO>5kb?Ld9dW| zdg6@Ewtmu`yjZ{XAz~U86LiL;UgD~O_|)T)xbuOc+oK)Djx||D@r!xhHeqh>N0MDs zU|C&ETm5p!SkzgmSNgZ@=gvc_^kn-9VE6G6;!6KiNd|Ro#c**j)O~P9dD1g9k!xY& zr}=xhFfMTkV9Xn}d|ujgBza0@bxMWrV+-^Y&)VZsb}Q!!nB|1K&vL;1(7ti*@X?QT z2Y*1;a^Ag9y(BxjL=4*tTW)OZQ;#o4`n_NzJ<4FIh>m(OUvK87w}0qo;|Wey9!wCN z7sL*a+SHAhp!=A*k9FP_8139|l7Xr;`pz#IqBg60QZ7sApw;5b*9!Njc=bzvx83?& zHi|kkU4(zbO5UfDD9o&2u=Hi_L^^=l&nn1osf16rJegHhl!gcI3!fouS*3~iextv; zk^)|bm$`rIQrqyy@9W6G!QT&(x^^T>FTl4LTu;FVTJNxPi~0I8Fzh^cDGRNT`gFSl~&|ogf zoYkz^bcik4v)Cz=(oRJ;m8=KDSnQO^*fk(C8Y}@es4T;=Ftq35~ou(jFZ7#9ae4n6dJA);BFfhBJ_A)y7 zYx~a+@}bpgTPz(9{Y!%yrIdS2ia6fzoQ|mGk;dZ(pO$R$0f7sSoS?eqdi{D41vvb>yX3UuOOT@hj=9jlO4;j^%nf zE>$#u^G3VrduaQ&k4jw1HAiHGLi6mLA281GFLHfYXE!daBXZU5oqCVz+D0XgZ9)Pn zKDRzO9VJ_NDH&FZTB(jV_RDkB=sYknxM7PrVI)lRnjf!$hxs`s%<@zo`9*3(9GCO@ zHc_8M)`I*P)Y8wtcR9rWtD{063t9~S+!f&^ux6cC>kE0-Bvku=lafEoh`kzhqW5Qm z!Bvd6u^8mtnl_tGr1)lXB|`as zaMg#lMAoHoX3HSBE0G-JH{P&q}Pl2kRH9{H^SWPLknZF zxEM7=Bq5;@J0G_9S((|6h|Hvslke+Vh^dA{#86mDRNxICoqi+(;pG0mt^TjRM5Z+tTIMkJ{j>0I z-$V~8dJyZrgV?B{URl&~{Uo^93g1V?lJpnD_f9kP1YXxrXa?D0bzAz%+l=q}Q%Wgs z0Erc418(j9V3n7?v1Hw(Nx5Z$^RtH%dPEc)F;Ik8{#Dyazzs&2wBYjFbxf<&6qXes&>yh+(B7TvHYS|#9Nxp_^hmq^Cd4sjQz8ugIvCC z&N2rKrej7ni!}?#Xu!0h&B4%zb0ZATBuQ_1Rq%_08TRo zd_}jG!|$lH>KB$>;TQ}nm*l>C-VjoM^R_HpmR9835t))441?HK*=w4>t zu3I|F2?9PAQMpIIwSK05IKY~=^AWyFel*|8&>pfgtb*OHg0r{BG1SfXyHJX%ufIlE zlO=fRp}9Xy?JE>X4{qAE#e4(GUB|(`dkV*a!OuKN#-2Cl2$qqh`kNPf zgX&(m68=mpcmAE(PVbkck`Z z(Ir*(^&+=<-bmiy0(+{CcfWG3&hN8 z=6z(lZg_#A&V`wjr5ZNe7o~g1uT8B(hvVk}sMO3}!18}ckn^gZuR7Mg;-9)hXrnPeTiD|D76I78r^JJW+ zJs6FZsvZ}kT{e;V;Oijr>lz!T3c8&e8~$2vZ++KT2BqG_7)CCptPTF$9R{>CCL2?8 zzzpB(R5W^*_6_vDgs*d}FyDpT$rf-)MdmRJU$4J~0(-g7{5U8jyZhF~8k$yy9jP@N z78hw1h;QGp&(#$;6 zYnhJ8p2iYQm`0-rXG}1vxk)=z#D1w;pLRf3o~nD9y@T=#m;4sweL!PN+mIrBy*Is1 zpWZrqU_Z-jRdi@=7b&3Tv$mUk)RW0!tLk~xZClt|0^-+9qwUK$Lnk<_aZZs z8*jU^xje%|Uw~Tb+QL_YuQ!Xl&8#54c5T03cbSmYtUxrFAbhsu`i!;j`rj(Z-_fFy z65iv~oxN%>ze_^^c7fta`lEVGO>eu7S@TsG!LU@FgOjM6H5Ru(gtQdA=75#y;+Cr~ zbGx2{U>yuA?)Pq%QtlAW93D-O*~f~>tIa4RRZjh#a`U;|BJl}zFmi_W7%0Ktnjzsb z_Pv0Tm~DKb+~XhRvBg4ttu`^s(?(MLoqygrtEZ(bDF=V1y6kx9pAAPsR_nAU37gr! z=T*MfWP{nzdC3~O|ttw}fCt8mjaP9B*^%*)i@-%&tjC)8_baSTX% zAL!N;oj*>R;Ag#1vwhiV z;*X}(9^24`+A|8BkBD7k#BAa~mDR006*6mM&fz&uE&?!R*^R}vh7=l#BtHL z4yQ=OB=!B0WYf~Cv7vY5TXN{f;=@MXkYDl>4&n7ZbNXmA)b?Eg_>DHNZYal_sv}nZ=v=tfVUw+B$Sm*KJt2|mP6fJXSKIO6 z%IDY)mpK5(M2*F0iYoKo2B5~fXrxA~^%QdrJO{HfDh2}JC*C=?I98*jDkRY|!gbHJ zK3E=UaToxym2w?UO6OitCfia=c6j;yz5aN21@g{Z_;1I?UH8t+fyKw&z2{<)fwpV| z;;*c<>%=PG?)QA2DNm`0Zhx~ECp|~yCLrz7;@Y4$CJ-5%GByv^p@WqoFFjR!3cY{F zSli27ZnP&F=kUA|Hoq_F5*<5i$gbYC3|!Od&!rZD4BBT?fSG)W*1=h%ud)k_DQb^Q zXchy}?U#qXYo-IaK{gl@;EZ+LdU<4zPao~EScx?J-P2Fmf9Y99#~F>0Wu*Cy$=(0N zwT_N61j3ZjV()3MI5z7O;WKD7o}-N?k-PPI559HVCMG-1K8aGt7nT0p`XDjCQ<*|= zHE7)p@CAg=Im9HQi>@SCI#|moyBo|r1t}91Rzh*Qc7XXin;n!Je-6NfviR?}95mXaF^&b*PtuTPLt_FT5c*Y+(et{#tt= zRFbg&sQtmHDdULUIq$aGr_ysawg;2XiVF5_io|ATKyKs6@l8smz*<4t(Kaob3Yd&v z^Kz-@1Gr%uWxSI3`;C(d#V2KcucQ&(p>D(F;`Jz5?7&>dz4<`5i4<`G)>O{EpPxW5 zDmK>1R(;`+{6(8TdPdr83JboPe_jQT`KxyrKU(Ud8S40KyomO!E2iHVC{T$DPb{GN z@zs5N)tn4-7AqD5x?uTd^e+3pug-5u^@-LOFcA;*?^|w`Dq2}2=37VHom1Q!#Jdx$ z((Akjh07e?*V-*rQY%U1er>FgqjA`q3z<_sK<}_YDp7BM4)lH`_nHyE9JsiSzfdH*}@-_whvO31Z?! zISu&|$dAp}qs4LvQ{>a9Cy#;-C0|K4&BdWpu3Qs-8U-Q!Xqr5h7=0nAmmuUVH^8<` z%3{Eg45Bt9+TMfmU=8yzSgO+Q2uSZEkrKDU?U4pIOM@PRtKa^+(CfCXSNnvwwiWO% zgyRd*_AB@;_w$Dx7*sjxZ#s>K-gO!;)XIvqhlIiZsF^=(sXwAaormKW{wCGe<{t=KaZYT{%#(U-P zS=?6*ZD{_!Y!&3?JxlRR43?{Xtcqnl`Dgs-$N0u=D#Qaw0|mtF9A z-I=iFp%W1+99JMXksyiJQ$$y+VAZy6!?ge_V9}}s@$5XS%YS5)qfIx99*MB@b*d@B z|8yZJ${dV#pX%J>{&9alsP0DL+3Y@GeQut&-T6qzTA&rvXN5;rlT`=o!e^_^o=Y3D zpM#8-74aM)7C?9Pa=y_WXKREpecQVbLKPxmv zX<=y@1X**znN#>WqrW#uK2k~wXS2|6BfjNzTZaw+bkS&PDBf@pU2 zOQ1ThQD>t!WeJzvb@iY{d)Nh&nx7p7)l4Cyj-8N~A*{tokawozwL79BMRTe9US?3^ zl~9631qyaVtOK?X+QDd>N22Xaa5n|K();K;h3$hW`WPTVJ-)b&Tg%Tl48m5lwye&2 z@Y5ZMdnLw+fK{7kt5S(Dbw&V99)Dext7r@gaLEb5`@}88pE8Qey1i3R z+Aj%en@!5tL4g3aq03wu!2IK0Q{llgp}zVv_YkUCe0a_aY-Of8BccSK)4tyvshUe& znBofR2~irJxNyRfWXyKm-38<0`WeAl7^fZTyLm&hs|lx#HZ99(tScgxH!R&oXHeVO zMf?o`&Hm}3Lsr2C9LeG7IXl6tUUY-t!_j^_!n28Kfb#fXfG(n7b=&d(U#`C&b&!qX z+`gaA0yDeMZS9wo-YYmPu1%yv1H)bWvgJg=pTf4v)~agOVOHA*{JiU7f!jn#vhG98 z^wr&JbH}S>*OU4mn=USM?*YJxvb>2r!(jQMOy5Jp`g9EA*)E4l03Z#WMXjue;S^t`xImdj$g^W^0{WWCsUfD)#1l>c!&@DnseyCj<#Jx9S4 z5y_SrWR2@45ghix5wWK&(~+t#Lw?i>qc9ZE&U!=Qjrc_?z*^|2#M*mC)0p1eegAI^ z|37r--%{TH5iD5r$5h8rKz5QFyJHG@=f&{ViIy*F?pmf^TRDVO7yK&ETGU0eyAsji zzwt9Pf~JLo6Ff7OypYUL_jvCSq7Rh~0y+ajn2;RdgIl2jQb%<&I}jWx)|CPr!zYj` zhJ~)bk!Sbg=2L8XP*@!s>b?>TBX$d>bU z)rieWwUTIe9N#`4Dlj-H#R@x8f`qc)_6(RHw7^|&;LJ4rd#bnT^XH$lzdb16KPE7f z2zVgQpcgxhADV_-=n}zyu$<2k(-$0v>WX#K?}F?A z{rWh+Z*njUcitxXmRI=<9vvVEm7M&ZlEr1Z49df6If?S)xt>UHxwL|O*VwRcB`Rux z-GSKFto1zpOoS=!P8eGtnjoiv0mYEB(A<4n3FH}Fv&u8TKeWSt@v-kC4Zn4N%cN%f zV}Q?Q!)*R-g9U+~Dm!`GV4^Pn!;FGj)Z>?Z(56*w^JmegLn&Tfv@iBExZ@7%>$-3=Te>V|CegYqJ z3+;Q?ZKR`6^et_6&+2YzieXm8ox(zLcANfun;gpQZ?RYI6B5|XyT?eDIQ)n&=^|}6 zu^VQfrSKT}3o&0yEBylIai`fsD*%|zIArwY3e$*+G3TK}gQ+hF;#K$mWxRu;7doKR zTJ)Yaz=$X#CDqr=U90a^GXp*#H^^~5OAV<_an&(~G<4ytA_XO>lkatp(rF3Y9YIvq z-s*stA(*xhMiHtXt969}@c4W&h2l7X$6{>C9Q8ZXcpBnRu&XnDW+CV^UIu2*zu6J; z28#>W=O)vRgkK~Q)N=LBSZKbM3|0!UY1Z1yypKrUmL;$F?ao9Pa;DJ83t|7t+mWv- zjdkd1#xBW;Ejv}RuEg?{;I%)RdVCGiD|$}%rho6%bhy?~nfU&xZ>ub`A0^OU!{JPw z4>;;jvqHjtiqtV0YnCkPRPxcg_b!+;O_Tphq6% zvQu@16MnhAhb2Nd3rk7{a3Q?I@?4B7`hxjw+e`99%%$hsT2GEF!)>FOTroxEHA&U3 z+|~zC>aYvXofb*NdHat#+Z|Re;EuF9E_$U2Pq>;J!Iw_mv-KKo`hs&rtC#xA#ydET zr=8`E8L|!J-Tkwz|D0V#{qOI{Hj~4DP4`*ac2|rz=GCy3m%kZl^D_zVQl0feDTsKZ zYZ6Pws0-WgmXM34?PEIpqCw9h#uB%tEgAgm$ew^`{%$#W?PLJ)3sGTj%gJP6MOwAe zb8ONu0QxjO#@9Imh_^PhBuOlD`T3fGly4Ybw{^XF?VYR$Vjb+RL*;K9<7;94>fNXt zL(&ygltA74+_(S!d9Th@BskK(NR~*k9WWGd!gjpJigU(}YrN(!?TkLUTV2?^n z*Ranj#_6dDz5U^p7C@z)*vmJjyC1%P`DnLV5MpZ0D0c5OrSnj~)skV1&)AC48z8BQ zN)lH^Dpp-fwUu$xX`D0-di68LMkcqQT%S(b+g;M>Ny~Rg1<+IY zSYDoWn;B8=C(X|Aj`7NSMd&G=0~?>I6ff0%r2vvS>FXuLNra^3=Bi4BWhO#Sz|$7# z8!PaO3hKsF;xEe-a0M7gB#MIC1Dt50=y*7jRQ1$to~rWY=lKy&5%#vp6h0N-U~nvu z-t?rs-_b}IM+5@UXY7W&Y(- zQ?sE2-`o1MnNxbB8cc+{hKrDztkO6=szxj`T9C+CDHlMP!vx0dgJg66q2yZF$W!Oh zGn7`LI(xL+3a3~HXIR31g`1}K1-^9tN-P+1P&Db<_b150`&D)Of2GLq`1ilkl>Y@~ zKA0w*q&qfmceBDRN)hI zN>ZwlQ7=$gBqVCo`$zM3KCatkVW*_uXUlT{b{9mklRpG^ulZbjIGvD|j9)i(2*JoP zB>0}j`wuYwFL~Bqcl!6AEIZvhsEVxWne;yLWa##94x)5fuRDWK*735BS(^f*{sQnE zUcvb2%X)*czIGgrdG7bC>WCdMBnw3cZhP`r2>x_gn6BAeLT+qNn-1a{&QN|L+)G)V zFh*%X+c#hS8~}H7Xt16;+e9|23F_PjN~0@g_101A@EeYpXxyx)C7_sVY~VoqQZ)=t==RK|e;s*O!#q1N^} zY{giGS!uNShujSBU$LF7(X2#;2_5xtzS(s8fz^hTh8FCseC>hpw2W{s3a+UqdL{NE z7BWa8@04%}w+28B`e)2wa*rtb0qyP#@^$7mRbqc?IG^zf@@1HcyN&}Nln5>bMLRGc5Y!&$^#RHhI@zA zsDrfyxJaJcdt*u(zugdK30{vNUd%)j)XW{($P&eFLI*)e5@OYvcT3LoxZ&MzSmFUrMJP93V>^AB0dfL@ewHJ+jbOF(r@>YD*m# zmu}(}qo^?ZMit%@7|Zz;a!%JsL{8p9FcRF%;;l5+QO=r`ELLx&U{eoLE0P}jYR#4L zo2f^kshY=-la!}e?&%fiA^d;*7m$Z-j$4@c1V${({vo4CW=t)e;8D<)dmS$?{0J>U+P zwW83#sQ^1aR(JP)1f!8rb-0L^;rY*y z64Y~6IaeHA^Gk$PX|_?V?H&X~+&`Il7KB#>8-J1GeMh8wOU9)BIjR>0Zm#!59PWa* z#~yg{jT4KWd@3~`i3{QPQfSkuNZQmd8PoEc>AH%Tj0!W`o0;HT^D&1@ahhRN? zdcODr81#=j3VtgNv*YsmY~wzNV)(wuXLCkGaw-a2Y|C^1kOA`W5sRgcSDwjlw@h_7 z)v|t$QZk@|cE}P+&r6OJIBn$hx4`W`lK?+oUVkJv`L+opYR@^{){Nx{K^Y(qrJg6I z96n?FI9vU~W!8r`fiiC1z-96Ej9YOHH%Zs<{9CFR-VOput(TkA$k@8i?h1xPBDPBi zf6Z75XWEK-QCy`F=W15m#_x0GptwvvX|nQ|We<%*Z;f#x%q72Pv9YLK^3((MCMqZw z=^!fptsYR-fa7VygBV)Asi7J@awl*CXo@r#>5^Nol7~OJRnf?MlV4km2 z1EWN?^ZTiV&HwafMp*9q1NMULbwBQ;oaHivn(0I$fk zknlP~c9!cUL$*_vB;L9pX`aIl^A*HzR1xQhcP9b~_Xz%6M#?R;Rwy8AZ7PZ>w*1U* zm&ZwrDD)%y+6d_rtO$Am$1w7NSlOv6OE5|$r%tlO`Aytyk+7PsdJr*tw1={ z;+O=ZLq~QF8RL%A7~)9UdFF08L&W8x>ZminE?er1F99Z!P|dC@W?W)Mc4kx)?fl{- zD!tO0fT@c-!wpb1>Y2co#tBM=VruJB4)0j+M zE(-_d zzt>$bP%^t8WI`OXlrEm2Imf)bHH`c!Blkc?Oi{v$d2FJX2VBnb74RPYc#vu}^}`LiS29Q-#<9J|4-+W{#P$|ZcTUWE zaJ#(J#A{`a>Dm$h7Cqe%0D8D3JcasE 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 literal 0 HcmV?d00001 diff --git a/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Expression.java b/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Expression.java new file mode 100644 index 0000000..a41ab94 --- /dev/null +++ b/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Expression.java @@ -0,0 +1,1100 @@ +package nyc.c4q.sufeiiz.scientificcalculator; + +/* + * Copyright 2012 Udo Klimaschewski + * + * http://UdoJava.com/ + * http://about.me/udo.klimaschewski + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +/** + *

EvalEx - Java Expression Evaluator

+ * + *

Introduction

+ * EvalEx is a handy expression evaluator for Java, that allows to evaluate simple mathematical and boolean expressions. + *
+ * Key Features: + *
    + *
  • Uses BigDecimal for calculation and result
  • + *
  • Single class implementation, very compact
  • + *
  • No dependencies to external libraries
  • + *
  • Precision and rounding mode can be set
  • + *
  • Supports variables
  • + *
  • Standard boolean and mathematical operators
  • + *
  • Standard basic mathematical and boolean functions
  • + *
  • Custom functions and operators can be added at runtime
  • + *
+ *
+ *

Examples

+ *
+ *  BigDecimal result = null;
+ *
+ *  Expression expression = new Expression("1+1/3");
+ *  result = expression.eval():
+ *  expression.setPrecision(2);
+ *  result = expression.eval():
+ *
+ *  result = new Expression("(3.4 + -4.1)/2").eval();
+ *
+ *  result = new Expression("SQRT(a^2 + b^2").with("a","2.4").and("b","9.253").eval();
+ *
+ *  BigDecimal a = new BigDecimal("2.4");
+ *  BigDecimal b = new BigDecimal("9.235");
+ *  result = new Expression("SQRT(a^2 + b^2").with("a",a).and("b",b).eval();
+ *
+ *  result = new Expression("2.4/PI").setPrecision(128).setRoundingMode(RoundingMode.UP).eval();
+ *
+ *  result = new Expression("random() > 0.5").eval();
+ *
+ *  result = new Expression("not(x<7 || sqrt(max(x,9)) <= 3))").with("x","22.9").eval();
+ * 
+ *
+ *

Supported Operators

+ * + * + * + * + * + * + * + * + * + *
Mathematical Operators
OperatorDescription
+Additive operator
-Subtraction operator
*Multiplication operator
/Division operator
%Remainder operator (Modulo)
^Power operator
+ *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Boolean Operators*
OperatorDescription
=Equals
==Equals
!=Not equals
<>Not equals
<Less than
<=Less than or equal to
>Greater than
>=Greater than or equal to
&&Boolean and
||Boolean or
+ * *Boolean operators result always in a BigDecimal value of 1 or 0 (zero). Any non-zero value is treated as a _true_ value. Boolean _not_ is implemented by a function. + *
+ *

Supported Functions

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Function*Description
NOT(expression)Boolean negation, 1 (means true) if the expression is not zero
IF(condition,value_if_true,value_if_false)Returns one value if the condition evaluates to true or the other if it evaluates to false
RANDOM()Produces a random number between 0 and 1
MIN(e1,e2)Returns the smaller of both expressions
MAX(e1,e2)Returns the bigger of both expressions
ABS(expression)Returns the absolute (non-negative) value of the expression
ROUND(expression,precision)Rounds a value to a certain number of digits, uses the current rounding mode
FLOOR(expression)Rounds the value down to the nearest integer
CEILING(expression)Rounds the value up to the nearest integer
LOG(expression)Returns the natural logarithm (base e) of an expression
LOG10(expression)Returns the common logarithm (base 10) of an expression
SQRT(expression)Returns the square root of an expression
SIN(expression)Returns the trigonometric sine of an angle (in degrees)
COS(expression)Returns the trigonometric cosine of an angle (in degrees)
TAN(expression)Returns the trigonometric tangens of an angle (in degrees)
SINH(expression)Returns the hyperbolic sine of a value
COSH(expression)Returns the hyperbolic cosine of a value
TANH(expression)Returns the hyperbolic tangens of a value
RAD(expression)Converts an angle measured in degrees to an approximately equivalent angle measured in radians
DEG(expression)Converts an angle measured in radians to an approximately equivalent angle measured in degrees
+ * *Functions names are case insensitive. + *
+ *

Supported Constants

+ * + * + * + * + * + *
ConstantDescription
PIThe value of PI, exact to 100 digits
TRUEThe value one
FALSEThe value zero
+ * + *

Add Custom Operators

+ * + * Custom operators can be added easily, simply create an instance of `Expression.Operator` and add it to the expression. + * Parameters are the operator string, its precedence and if it is left associative. The operators `eval()` method will be called with the BigDecimal values of the operands. + * All existing operators can also be overridden. + *
+ * For example, add an operator `x >> n`, that moves the decimal point of _x_ _n_ digits to the right: + * + *
+ * Expression e = new Expression("2.1234 >> 2");
+ *
+ * e.addOperator(e.new Operator(">>", 30, true) {
+ *     {@literal @}Override
+ *     public BigDecimal eval(BigDecimal v1, BigDecimal v2) {
+ *         return v1.movePointRight(v2.toBigInteger().intValue());
+ *     }
+ * });
+ *
+ * e.eval(); // returns 212.34
+ * 
+ *
+ *

Add Custom Functions

+ * + * Adding custom functions is as easy as adding custom operators. Create an instance of `Expression.Function`and add it to the expression. + * Parameters are the function name and the count of required parameters. The functions `eval()` method will be called with a list of the BigDecimal parameters. + * All existing functions can also be overridden. + *
+ * For example, add a function `average(a,b,c)`, that will calculate the average value of a, b and c: + *
+ *
+ * Expression e = new Expression("2 * average(12,4,8)");
+ *
+ * e.addFunction(e.new Function("average", 3) {
+ *     {@literal @}Override
+ *     public BigDecimal eval(List parameters) {
+ *         BigDecimal sum = parameters.get(0).add(parameters.get(1)).add(parameters.get(2));
+ *         return sum.divide(new BigDecimal(3));
+ *     }
+ * });
+ *
+ * e.eval(); // returns 16
+ * 
+ * The software is licensed under the MIT Open Source license (see LICENSE file). + *
+ *
    + *
  • The *power of* operator (^) implementation was copied from [Stack Overflow](http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java) Thanks to Gene Marin
  • + *
  • The SQRT() function implementation was taken from the book [The Java Programmers Guide To numerical Computing](http://www.amazon.de/Java-Number-Cruncher-Programmers-Numerical/dp/0130460419) (Ronald Mak, 2002)
  • + *
+ * + *@author Udo Klimaschewski (http://about.me/udo.klimaschewski) + */ +public class Expression { + + /** + * Definition of PI as a constant, can be used in expressions as variable. + */ + public static final BigDecimal PI = new BigDecimal( + "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"); + + /** + * The {@link java.math.MathContext} to use for calculations. + */ + private MathContext mc = MathContext.DECIMAL32; + + /** + * The original infix expression. + */ + private String expression = null; + + /** + * The cached RPN (Reverse Polish Notation) of the expression. + */ + private List rpn = null; + + /** + * All defined operators with name and implementation. + */ + private Map operators = new HashMap(); + + /** + * All defined functions with name and implementation. + */ + private Map functions = new HashMap(); + + /** + * All defined variables with name and value. + */ + private Map variables = new HashMap(); + + /** + * What character to use for decimal separators. + */ + private final char decimalSeparator = '.'; + + /** + * What character to use for minus sign (negative values). + */ + private final char minusSign = '-'; + + /** + * The expression evaluators exception class. + */ + public class ExpressionException extends RuntimeException { + private static final long serialVersionUID = 1118142866870779047L; + + public ExpressionException(String message) { + super(message); + } + } + + /** + * Abstract definition of a supported expression function. A function is + * defined by a name, the number of parameters and the actual processing + * implementation. + */ + public abstract class Function { + /** + * Name of this function. + */ + private String name; + /** + * Number of parameters expected for this function. + */ + private int numParams; + + /** + * Creates a new function with given name and parameter count. + * + * @param name + * The name of the function. + * @param numParams + * The number of parameters for this function. + */ + public Function(String name, int numParams) { + this.name = name.toUpperCase(); + this.numParams = numParams; + } + + public String getName() { + return name; + } + + public int getNumParams() { + return numParams; + } + + /** + * Implementation for this function. + * + * @param parameters + * Parameters will be passed by the expression evaluator as a + * {@link List} of {@link BigDecimal} values. + * @return The function must return a new {@link BigDecimal} value as a + * computing result. + */ + public abstract BigDecimal eval(List parameters); + } + + /** + * Abstract definition of a supported operator. An operator is defined by + * its name (pattern), precedence and if it is left- or right associative. + */ + public abstract class Operator { + /** + * This operators name (pattern). + */ + private String oper; + /** + * Operators precedence. + */ + private int precedence; + /** + * Operator is left associative. + */ + private boolean leftAssoc; + + /** + * Creates a new operator. + * + * @param oper + * The operator name (pattern). + * @param precedence + * The operators precedence. + * @param leftAssoc + * true if the operator is left associative, + * else false. + */ + public Operator(String oper, int precedence, boolean leftAssoc) { + this.oper = oper; + this.precedence = precedence; + this.leftAssoc = leftAssoc; + } + + public String getOper() { + return oper; + } + + public int getPrecedence() { + return precedence; + } + + public boolean isLeftAssoc() { + return leftAssoc; + } + + /** + * Implementation for this operator. + * + * @param v1 + * Operand 1. + * @param v2 + * Operand 2. + * @return The result of the operation. + */ + public abstract BigDecimal eval(BigDecimal v1, BigDecimal v2); + } + + /** + * Expression tokenizer that allows to iterate over a {@link String} + * expression token by token. Blank characters will be skipped. + */ + private class Tokenizer implements Iterator { + + /** + * Actual position in expression string. + */ + private int pos = 0; + + /** + * The original input expression. + */ + private String input; + /** + * The previous token or null if none. + */ + private String previousToken; + + /** + * Creates a new tokenizer for an expression. + * + * @param input + * The expression string. + */ + public Tokenizer(String input) { + this.input = input.trim(); + } + + @Override + public boolean hasNext() { + return (pos < input.length()); + } + + /** + * Peek at the next character, without advancing the iterator. + * + * @return The next character or character 0, if at end of string. + */ + private char peekNextChar() { + if (pos < (input.length() - 1)) { + return input.charAt(pos + 1); + } else { + return 0; + } + } + + @Override + public String next() { + StringBuilder token = new StringBuilder(); + if (pos >= input.length()) { + return previousToken = null; + } + char ch = input.charAt(pos); + while (Character.isWhitespace(ch) && pos < input.length()) { + ch = input.charAt(++pos); + } + if (Character.isDigit(ch)) { + while ((Character.isDigit(ch) || ch == decimalSeparator) + && (pos < input.length())) { + token.append(input.charAt(pos++)); + ch = pos == input.length() ? 0 : input.charAt(pos); + } + } else if (ch == minusSign + && Character.isDigit(peekNextChar()) + && ("(".equals(previousToken) || ",".equals(previousToken) + || previousToken == null || operators + .containsKey(previousToken))) { + token.append(minusSign); + pos++; + token.append(next()); + } else if (Character.isLetter(ch) || (ch == '_')) { + while ((Character.isLetter(ch) || Character.isDigit(ch) || (ch == '_')) && (pos < input.length())) { + token.append(input.charAt(pos++)); + ch = pos == input.length() ? 0 : input.charAt(pos); + } + } else if (ch == '(' || ch == ')' || ch == ',') { + token.append(ch); + pos++; + } else { + while (!Character.isLetter(ch) && !Character.isDigit(ch) && ch != '_' + && !Character.isWhitespace(ch) && ch != '(' + && ch != ')' && ch != ',' && (pos < input.length())) { + token.append(input.charAt(pos)); + pos++; + ch = pos == input.length() ? 0 : input.charAt(pos); + if (ch == minusSign) { + break; + } + } + if (!operators.containsKey(token.toString())) { + throw new ExpressionException("Unknown operator '" + token + + "' at position " + (pos - token.length() + 1)); + } + } + return previousToken = token.toString(); + } + + @Override + public void remove() { + throw new ExpressionException("remove() not supported"); + } + + /** + * Get the actual character position in the string. + * + * @return The actual character position. + */ + public int getPos() { + return pos; + } + + } + + /** + * Creates a new expression instance from an expression string. + * + * @param expression + * The expression. E.g. "2.4*sin(3)/(2-4)" or + * "sin(y)>0 & max(z, 3)>3" + */ + public Expression(String expression) { + this.expression = expression; + addOperator(new Operator("+", 20, true) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.add(v2, mc); + } + }); + addOperator(new Operator("-", 20, true) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.subtract(v2, mc); + } + }); + addOperator(new Operator("*", 30, true) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.multiply(v2, mc); + } + }); + addOperator(new Operator("/", 30, true) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.divide(v2, mc); + } + }); + addOperator(new Operator("%", 30, true) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.remainder(v2, mc); + } + }); + addOperator(new Operator("^", 40, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + /*- + * Thanks to Gene Marin: + * http://stackoverflow.com/questions/3579779/how-to-do-a-fractional-power-on-bigdecimal-in-java + */ + int signOf2 = v2.signum(); + double dn1 = v1.doubleValue(); + v2 = v2.multiply(new BigDecimal(signOf2)); // n2 is now positive + BigDecimal remainderOf2 = v2.remainder(BigDecimal.ONE); + BigDecimal n2IntPart = v2.subtract(remainderOf2); + BigDecimal intPow = v1.pow(n2IntPart.intValueExact(), mc); + BigDecimal doublePow = new BigDecimal(Math.pow(dn1, + remainderOf2.doubleValue())); + + BigDecimal result = intPow.multiply(doublePow, mc); + if (signOf2 == -1) { + result = BigDecimal.ONE.divide(result, mc.getPrecision(), + RoundingMode.HALF_UP); + } + return result; + } + }); + addOperator(new Operator("&&", 4, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + boolean b1 = !v1.equals(BigDecimal.ZERO); + boolean b2 = !v2.equals(BigDecimal.ZERO); + return b1 && b2 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + + addOperator(new Operator("||", 2, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + boolean b1 = !v1.equals(BigDecimal.ZERO); + boolean b2 = !v2.equals(BigDecimal.ZERO); + return b1 || b2 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + + addOperator(new Operator(">", 10, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.compareTo(v2) == 1 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + + addOperator(new Operator(">=", 10, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.compareTo(v2) >= 0 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + + addOperator(new Operator("<", 10, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.compareTo(v2) == -1 ? BigDecimal.ONE + : BigDecimal.ZERO; + } + }); + + addOperator(new Operator("<=", 10, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.compareTo(v2) <= 0 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + + addOperator(new Operator("=", 7, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.compareTo(v2) == 0 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + addOperator(new Operator("==", 7, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return operators.get("=").eval(v1, v2); + } + }); + + addOperator(new Operator("!=", 7, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return v1.compareTo(v2) != 0 ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + addOperator(new Operator("<>", 7, false) { + @Override + public BigDecimal eval(BigDecimal v1, BigDecimal v2) { + return operators.get("!=").eval(v1, v2); + } + }); + + addFunction(new Function("NOT", 1) { + @Override + public BigDecimal eval(List parameters) { + boolean zero = parameters.get(0).compareTo(BigDecimal.ZERO) == 0; + return zero ? BigDecimal.ONE : BigDecimal.ZERO; + } + }); + + addFunction(new Function("IF", 3) { + @Override + public BigDecimal eval(List parameters) { + boolean isTrue = !parameters.get(0).equals(BigDecimal.ZERO); + return isTrue ? parameters.get(1) : parameters.get(2); + } + }); + + addFunction(new Function("RANDOM", 0) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.random(); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("SIN", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.sin(Math.toRadians(parameters.get(0) + .doubleValue())); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("COS", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.cos(Math.toRadians(parameters.get(0) + .doubleValue())); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("TAN", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.tan(Math.toRadians(parameters.get(0) + .doubleValue())); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("SINH", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.sinh(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("COSH", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.cosh(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("TANH", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.tanh(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("RAD", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.toRadians(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("DEG", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.toDegrees(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("MAX", 2) { + @Override + public BigDecimal eval(List parameters) { + BigDecimal v1 = parameters.get(0); + BigDecimal v2 = parameters.get(1); + return v1.compareTo(v2) > 0 ? v1 : v2; + } + }); + addFunction(new Function("MIN", 2) { + @Override + public BigDecimal eval(List parameters) { + BigDecimal v1 = parameters.get(0); + BigDecimal v2 = parameters.get(1); + return v1.compareTo(v2) < 0 ? v1 : v2; + } + }); + addFunction(new Function("ABS", 1) { + @Override + public BigDecimal eval(List parameters) { + return parameters.get(0).abs(mc); + } + }); + addFunction(new Function("LOG", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.log(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("LOG10", 1) { + @Override + public BigDecimal eval(List parameters) { + double d = Math.log10(parameters.get(0).doubleValue()); + return new BigDecimal(d, mc); + } + }); + addFunction(new Function("ROUND", 2) { + @Override + public BigDecimal eval(List parameters) { + BigDecimal toRound = parameters.get(0); + int precision = parameters.get(1).intValue(); + return toRound.setScale(precision, mc.getRoundingMode()); + } + }); + addFunction(new Function("FLOOR", 1) { + @Override + public BigDecimal eval(List parameters) { + BigDecimal toRound = parameters.get(0); + return toRound.setScale(0, RoundingMode.FLOOR); + } + }); + addFunction(new Function("CEILING", 1) { + @Override + public BigDecimal eval(List parameters) { + BigDecimal toRound = parameters.get(0); + return toRound.setScale(0, RoundingMode.CEILING); + } + }); + addFunction(new Function("SQRT", 1) { + @Override + public BigDecimal eval(List parameters) { + /* + * From The Java Programmers Guide To numerical Computing + * (Ronald Mak, 2003) + */ + BigDecimal x = parameters.get(0); + if (x.compareTo(BigDecimal.ZERO) == 0) { + return new BigDecimal(0); + } + if (x.signum() < 0) { + throw new ExpressionException( + "Argument to SQRT() function must not be negative"); + } + BigInteger n = x.movePointRight(mc.getPrecision() << 1) + .toBigInteger(); + + int bits = (n.bitLength() + 1) >> 1; + BigInteger ix = n.shiftRight(bits); + BigInteger ixPrev; + + do { + ixPrev = ix; + ix = ix.add(n.divide(ix)).shiftRight(1); + // Give other threads a chance to work; + Thread.yield(); + } while (ix.compareTo(ixPrev) != 0); + + return new BigDecimal(ix, mc.getPrecision()); + } + }); + + variables.put("PI", PI); + variables.put("TRUE", BigDecimal.ONE); + variables.put("FALSE", BigDecimal.ZERO); + + } + + /** + * Is the string a number? + * + * @param st + * The string. + * @return true, if the input string is a number. + */ + private boolean isNumber(String st) { + if (st.charAt(0) == minusSign && st.length() == 1) + return false; + for (char ch : st.toCharArray()) { + if (!Character.isDigit(ch) && ch != minusSign + && ch != decimalSeparator) + return false; + } + return true; + } + + /** + * Implementation of the Shunting Yard algorithm to transform an + * infix expression to a RPN expression. + * + * @param expression + * The input expression in infx. + * @return A RPN representation of the expression, with each token as a list + * member. + */ + private List shuntingYard(String expression) { + List outputQueue = new ArrayList(); + Stack stack = new Stack(); + + Tokenizer tokenizer = new Tokenizer(expression); + + String lastFunction = null; + String previousToken = null; + while (tokenizer.hasNext()) { + String token = tokenizer.next(); + if (isNumber(token)) { + outputQueue.add(token); + } else if (variables.containsKey(token)) { + outputQueue.add(token); + } else if (functions.containsKey(token.toUpperCase())) { + stack.push(token); + lastFunction = token; + } else if (Character.isLetter(token.charAt(0))) { + stack.push(token); + } else if (",".equals(token)) { + while (!stack.isEmpty() && !"(".equals(stack.peek())) { + outputQueue.add(stack.pop()); + } + if (stack.isEmpty()) { + throw new ExpressionException("Parse error for function '" + + lastFunction + "'"); + } + } else if (operators.containsKey(token)) { + Operator o1 = operators.get(token); + String token2 = stack.isEmpty() ? null : stack.peek(); + while (operators.containsKey(token2) + && ((o1.isLeftAssoc() && o1.getPrecedence() <= operators + .get(token2).getPrecedence()) || (o1 + .getPrecedence() < operators.get(token2) + .getPrecedence()))) { + outputQueue.add(stack.pop()); + token2 = stack.isEmpty() ? null : stack.peek(); + } + stack.push(token); + } else if ("(".equals(token)) { + if (previousToken != null) { + if (isNumber(previousToken)) { + throw new ExpressionException("Missing operator at character position " + tokenizer.getPos()); + } + } + stack.push(token); + } else if (")".equals(token)) { + while (!stack.isEmpty() && !"(".equals(stack.peek())) { + outputQueue.add(stack.pop()); + } + if (stack.isEmpty()) { + throw new RuntimeException("Mismatched parentheses"); + } + stack.pop(); + if (!stack.isEmpty() + && functions.containsKey(stack.peek().toUpperCase())) { + outputQueue.add(stack.pop()); + } + } + previousToken = token; + } + while (!stack.isEmpty()) { + String element = stack.pop(); + if ("(".equals(element) || ")".equals(element)) { + throw new RuntimeException("Mismatched parentheses"); + } + if (!operators.containsKey(element)) { + throw new RuntimeException("Unknown operator or function: " + + element); + } + outputQueue.add(element); + } + return outputQueue; + } + + /** + * Evaluates the expression. + * + * @return The result of the expression. + */ + public BigDecimal eval() { + + Stack stack = new Stack(); + + for (String token : getRPN()) { + if (operators.containsKey(token)) { + BigDecimal v1 = stack.pop(); + BigDecimal v2 = stack.pop(); + stack.push(operators.get(token).eval(v2, v1)); + } else if (variables.containsKey(token)) { + stack.push(variables.get(token).round(mc)); + } else if (functions.containsKey(token.toUpperCase())) { + Function f = functions.get(token.toUpperCase()); + ArrayList p = new ArrayList( + f.getNumParams()); + for (int i = 0; i < f.numParams; i++) { + p.add(0,stack.pop()); + } + BigDecimal fResult = f.eval(p); + stack.push(fResult); + } else { + stack.push(new BigDecimal(token, mc)); + } + } + return stack.pop().stripTrailingZeros(); + } + + /** + * Sets the precision for expression evaluation. + * + * @param precision + * The new precision. + * + * @return The expression, allows to chain methods. + */ + public Expression setPrecision(int precision) { + this.mc = new MathContext(precision); + return this; + } + + /** + * Sets the rounding mode for expression evaluation. + * + * @param roundingMode + * The new rounding mode. + * @return The expression, allows to chain methods. + */ + public Expression setRoundingMode(RoundingMode roundingMode) { + this.mc = new MathContext(mc.getPrecision(), roundingMode); + return this; + } + + /** + * Adds an operator to the list of supported operators. + * + * @param operator + * The operator to add. + * @return The previous operator with that name, or null if + * there was none. + */ + public Operator addOperator(Operator operator) { + return operators.put(operator.getOper(), operator); + } + + /** + * Adds a function to the list of supported functions + * + * @param function + * The function to add. + * @return The previous operator with that name, or null if + * there was none. + */ + public Function addFunction(Function function) { + return functions.put(function.getName(), function); + } + + /** + * Sets a variable value. + * + * @param variable + * The variable name. + * @param value + * The variable value. + * @return The expression, allows to chain methods. + */ + public Expression setVariable(String variable, BigDecimal value) { + variables.put(variable, value); + return this; + } + + /** + * Sets a variable value. + * + * @param variable + * The variable to set. + * @param value + * The variable value. + * @return The expression, allows to chain methods. + */ + public Expression setVariable(String variable, String value) { + if (isNumber(value)) + variables.put(variable, new BigDecimal(value)); + else { + expression = expression.replaceAll("\\b" + variable + "\\b", "(" + value + ")"); + rpn = null; + } + return this; + } + + /** + * Sets a variable value. + * + * @param variable + * The variable to set. + * @param value + * The variable value. + * @return The expression, allows to chain methods. + */ + public Expression with(String variable, BigDecimal value) { + return setVariable(variable, value); + } + + /** + * Sets a variable value. + * + * @param variable + * The variable to set. + * @param value + * The variable value. + * @return The expression, allows to chain methods. + */ + public Expression and(String variable, String value) { + return setVariable(variable, value); + } + + /** + * Sets a variable value. + * + * @param variable + * The variable to set. + * @param value + * The variable value. + * @return The expression, allows to chain methods. + */ + public Expression and(String variable, BigDecimal value) { + return setVariable(variable, value); + } + + /** + * Sets a variable value. + * + * @param variable + * The variable to set. + * @param value + * The variable value. + * @return The expression, allows to chain methods. + */ + public Expression with(String variable, String value) { + return setVariable(variable, value); + } + + /** + * Get an iterator for this expression, allows iterating over an expression + * token by token. + * + * @return A new iterator instance for this expression. + */ + public Iterator getExpressionTokenizer() { + return new Tokenizer(this.expression); + } + + /** + * Cached access to the RPN notation of this expression, ensures only one + * calculation of the RPN per expression instance. If no cached instance + * exists, a new one will be created and put to the cache. + * + * @return The cached RPN instance. + */ + private List getRPN() { + if (rpn == null) { + rpn = shuntingYard(this.expression); + } + return rpn; + } + + /** + * Get a string representation of the RPN (Reverse Polish Notation) for this + * expression. + * + * @return A string with the RPN representation for this expression. + */ + public String toRPN() { + String result = new String(); + for (String st : getRPN()) { + result = result.isEmpty() ? result : result + " "; + result += st; + } + return result; + } + +} + diff --git a/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/MainActivity.java b/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/MainActivity.java new file mode 100644 index 0000000..a20bfe0 --- /dev/null +++ b/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/MainActivity.java @@ -0,0 +1,898 @@ +package nyc.c4q.sufeiiz.scientificcalculator; + +import android.support.v7.app.ActionBarActivity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import java.math.BigDecimal; + + +public class MainActivity extends ActionBarActivity { + + private TextView outputEq, outputAns; + private String equation, currNum, lastAns, lastNum, display; + private int count; + private boolean degMode = true; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + outputEq = (TextView) findViewById(R.id.output_equation); + outputAns = (TextView) findViewById(R.id.output_answer); + if (savedInstanceState == null) { + outputEq.setText(""); + outputAns.setText(""); + display = ""; + equation = ""; + currNum = ""; + lastNum = ""; + lastAns = "0"; + count = 0; + } else { + outputAns.setText(savedInstanceState.getString("lastAns")); + outputEq.setText(savedInstanceState.getString("display")); + display = savedInstanceState.getString("display"); + equation = savedInstanceState.getString("equation"); + currNum = savedInstanceState.getString("currNum"); + lastNum = savedInstanceState.getString("lastNum"); + lastAns = savedInstanceState.getString("lastAns"); + count = savedInstanceState.getInt("count"); + } + + final Button clr = (Button) findViewById(R.id.btn_clear); + final Button ans = (Button) findViewById(R.id.btn_ans); + final Button negative = (Button) findViewById(R.id.btn_int); + final Button backspace = (Button) findViewById(R.id.btn_backspace); + final Button open = (Button) findViewById(R.id.btn_paren_open); + final Button close = (Button) findViewById(R.id.btn_paren_close); + final Button add = (Button) findViewById(R.id.btn_add); + final Button minus = (Button) findViewById(R.id.btn_sub); + final Button multiply = (Button) findViewById(R.id.btn_mult); + final Button divide = (Button) findViewById(R.id.btn_div); + final Button percent = (Button) findViewById(R.id.btn_percent); + final Button zero = (Button) findViewById(R.id.btn_0); + final Button one = (Button) findViewById(R.id.btn_1); + final Button two = (Button) findViewById(R.id.btn_2); + final Button three = (Button) findViewById(R.id.btn_3); + final Button four = (Button) findViewById(R.id.btn_4); + final Button five = (Button) findViewById(R.id.btn_5); + final Button six = (Button) findViewById(R.id.btn_6); + final Button seven = (Button) findViewById(R.id.btn_7); + final Button eight = (Button) findViewById(R.id.btn_8); + final Button nine = (Button) findViewById(R.id.btn_9); + final Button dot = (Button) findViewById(R.id.btn_decimal); + final Button equal = (Button) findViewById(R.id.btn_equals); + + // Scientific buttons + final Button factorial = (Button) findViewById(R.id.btn_factorial); + final Button pi = (Button) findViewById(R.id.btn_pi); + final Button e = (Button) findViewById(R.id.btn_e); + final Button sqRoot = (Button) findViewById(R.id.btn_sq_root); + final Button sq = (Button) findViewById(R.id.btn_sq); + final Button sin = (Button) findViewById(R.id.btn_sin); + final Button cos = (Button) findViewById(R.id.btn_cos); + final Button tan = (Button) findViewById(R.id.btn_tan); + final Button exp = (Button) findViewById(R.id.btn_exp); + final Button ln = (Button) findViewById(R.id.btn_ln); + final Button log = (Button) findViewById(R.id.btn_log); + + // Numbers OnClickListener + zero.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "0"; + equation += "0"; + display += "0"; + outputEq.setText(display); + } + }); + one.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "1"; + equation += "1"; + display += "1"; + outputEq.setText(display); + } + }); + two.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "2"; + equation += "2"; + display += "2"; + outputEq.setText(display); + } + }); + three.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "3"; + equation += "3"; + display += "3"; + outputEq.setText(display); + } + }); + four.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "4"; + equation += "4"; + display += "4"; + outputEq.setText(display); + } + }); + five.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "5"; + equation += "5"; + display += "5"; + outputEq.setText(display); + } + }); + six.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "6"; + equation += "6"; + display += "6"; + outputEq.setText(display); + } + }); + seven.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "7"; + equation += "7"; + display += "7"; + outputEq.setText(display); + } + }); + eight.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "8"; + equation += "8"; + display += "8"; + outputEq.setText(display); + } + }); + nine.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Numbers.lastInputIsConstant(display)) { + equation += "*"; + display += "×"; + } + currNum += "9"; + equation += "9"; + display += "9"; + outputEq.setText(display); + } + }); + + // if number already contains ".", ignore + // if user omits 0 before ".", include 0 + dot.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (currNum.contains(".")) ; + else if (currNum.equals("")) { + currNum += "0."; + equation += "0."; + display += "0."; + } else { + equation += "."; + currNum += "."; + display += "."; + } + outputEq.setText(display); + } + }); + + // clear everything + clr.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + equation = ""; + currNum = ""; + lastAns = ""; + lastNum = ""; + display = ""; + outputAns.setText(""); + outputEq.setText(display); + } + }); + + // TODO fix backspace for sincostan once that's implemented + // if equation is empty, do nothing + // else if the last input was %, convert to decimal to find length of converted num + // else delete last char in currNum, equation, and display + backspace.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) ; + else if (display.charAt(display.length() - 1) == '%') { + display = display.substring(0, display.length() - 1); // removes "%" + + // find length of converted decimal and remove from equation; add back current num + String converted = Numbers.percentage(currNum, lastNum); + equation = equation.substring(0, equation.length() - converted.length()); + equation += currNum; + } else if (display.charAt(display.length() - 1) == '!') { + display = display.substring(0, display.length() - 1); // removes "!" + + String converted = String.valueOf(Numbers.factorial(Integer.valueOf(currNum))); + equation = equation.substring(0, equation.length() - converted.length()); + equation += currNum; + } else if (display.charAt(display.length() - 1) == 'e' || + display.charAt(display.length() - 1) == 'π') { + equation = equation.substring(0, equation.length() - 13); + currNum = ""; + display = display.substring(0, display.length() - 1); + } else if (display.charAt(display.length() - 1) == 's') { + equation = equation.substring(0, equation.length() - currNum.length()); + currNum = ""; + display = display.substring(0, display.length() - 3); + } else { + if (currNum.length() > 0) + currNum = currNum.substring(0, currNum.length() - 1); + if (equation.length() > 0) + equation = equation.substring(0, equation.length() - 1); + if (display.length() > 0) + display = display.substring(0, display.length() - 1); + } + outputEq.setText(display); + } + }); + + // if equation is empty and current is not empty, add current to equation + // else if only equation is empty, ignore (needed so there's no out of range error) + // else if last input was operator or (, replace + // else just add operator + add.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty() && !lastAns.isEmpty()) { + lastNum = lastAns; + equation = lastAns + "+"; + display = lastAns + "+"; + } else if (equation.isEmpty()) ; + else if (Numbers.lastInputIsOperator(equation)) { + equation = equation.substring(0, equation.length() - 1) + "+"; + display = display.substring(0, display.length() - 1) + "+"; + } else { + equation += "+"; + lastNum = currNum; + currNum = ""; + display += "+"; + } + outputEq.setText(display); + } + }); + minus.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty() && !lastAns.isEmpty()) { + lastNum = lastAns; + equation = lastAns + "-"; + display = lastAns + "-"; + } else if (equation.isEmpty()) ; + else if (Numbers.lastInputIsOperator(equation)) { + equation = equation.substring(0, equation.length() - 1) + "-"; + display = display.substring(0, display.length() - 1) + "-"; + } else { + equation += "-"; + lastNum = currNum; + currNum = ""; + display += "-"; + } + outputEq.setText(display); + } + }); + multiply.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty() && !lastAns.isEmpty()) { + lastNum = lastAns; + equation = lastAns + "*"; + display = lastAns + "×"; + } else if (equation.isEmpty()) ; + else if (Numbers.lastInputIsOperator(equation)) { + equation = equation.substring(0, equation.length() - 1) + "*"; + display = display.substring(0, display.length() - 1) + "×"; + } else { + equation += "*"; + lastNum = currNum; + currNum = ""; + display += "×"; + } + outputEq.setText(display); + } + }); + divide.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty() && !lastAns.isEmpty()) { + lastNum = lastAns; + equation = lastAns + "/"; + display = lastAns + "÷"; + } else if (equation.isEmpty()) ; + else if (Numbers.lastInputIsOperator(equation)) { + equation = equation.substring(0, equation.length() - 1) + "/"; + display = display.substring(0, display.length() - 1) + "÷"; + } else { + equation += "/"; + lastNum = currNum; + currNum = ""; + display += "÷"; + } + outputEq.setText(display); + } + }); + + // if equation is empty, ignore + // else if last input was operator or ( or %, ignore + // else convert percentage to number + percent.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) ; + else if (Numbers.lastInputIsOperator(equation) || + Numbers.lastInputIsPercent(equation)) ; + else { + equation = equation.substring(0, equation.length() - currNum.length()); + String converted = Numbers.percentage(currNum, lastNum); + + equation += converted; + display += "%"; + outputEq.setText(display); + } + } + }); + + // if equation is empty, add - accordingly + // else if last input was %, find length of converted number and add - accordingly + // else if last input was e or pi, find length of converted number and add - accordingly + // else if last input was ans, find length of converted number and add - accordingly + // else add - accordingly + negative.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) { + if (currNum.contains("-")) + currNum = currNum.substring(1); + else + currNum = "-" + currNum; + equation += currNum; + display += currNum; + } else if (Numbers.lastInputIsPercent(display)) { + String converted = Numbers.percentage(currNum, lastNum); + display = display.substring(0, display.length() - currNum.length() - 1); + equation = equation.substring(0, equation.length() - converted.length()); + if (currNum.contains("-")) { + currNum = currNum.substring(1); + display += currNum + "%"; + equation += converted.substring(1); + } else { + currNum = "-" + currNum; + display += currNum + "%"; + equation += "-" + converted; + } + } else if (display.charAt(display.length() - 1) == 'e' || + display.charAt(display.length() - 1) == 'π') { + char constant = display.charAt(display.length() - 1); + if (currNum.contains("-")) { + currNum = currNum.substring(1); + equation = equation.substring(0, equation.length() - 14) + currNum; + display = display.substring(0, display.length() - 2) + constant; + } else { + currNum = "-" + currNum; + equation = equation.substring(0, equation.length() - 13) + "-" + currNum; + display = display.substring(0, display.length() - 1) + "-" + constant; + } + } else if (display.charAt(display.length() - 1) == 's') { + if (currNum.equals("0")) { + display = display.substring(0, display.length() - 3) + "-" + "Ans"; + } else if (currNum.contains("-")) { + currNum = currNum.substring(1); + equation = equation.substring(0, equation.length() - currNum.length() - 1) + currNum; + display = display.substring(0, display.length() - 4) + "Ans"; + } else { + currNum = "-" + currNum; + equation = equation.substring(0, equation.length() - currNum.length() + 1) + currNum; + display = display.substring(0, display.length() - 3) + "-" + "Ans"; + } + } else if (display.charAt(display.length() - 1) == '^') { + if (currNum.contains("-")) { + currNum = currNum.substring(1); + display = display.substring(0, display.length() - currNum.length() - 1) + currNum; + equation = equation.substring(0, equation.length() - currNum.length() - 1) + currNum; + } else { + currNum = "-" + currNum; + display = display.substring(0, display.length() - currNum.length() + 1) + currNum; + equation = equation.substring(0, equation.length() - currNum.length() + 1) + currNum; + } + } else { + if (currNum.contains("-")) { + equation = equation.substring(0, equation.length() - currNum.length()); + display = display.substring(0, display.length() - currNum.length()); + currNum = currNum.substring(1); + } else { + equation = equation.substring(0, equation.length() - currNum.length()); + display = display.substring(0, display.length() - currNum.length()); + currNum = "-" + currNum; + } + equation += currNum; + display += currNum; + } + outputEq.setText(display); + } + }); + + // increase count for every opened parentheses + // if equation if empty, open parentheses + // else if last input was a number or last input was ), add multiply before open + // else just open + open.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) { + equation += "("; + display += "("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*("; + display += "×("; + } else { + display += "("; + equation += "("; + } + count++; + outputEq.setText(display); + } + }); + + // decrease count for every closed parentheses + // if equation if empty or count is less than zero or last input was operator, ignore + // else just close + close.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty() || count <= 0 || + Numbers.lastInputIsOperator(equation)) ; + else { + equation += ")"; + display += ")"; + count--; + outputEq.setText(display); + } + } + }); + + // save last answer (whenever = is clicked) + ans.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + lastNum = currNum; + currNum = lastAns; + if (lastAns.isEmpty()) ; + else if (display.isEmpty()) { + equation += lastAns; + display += "Ans"; + } else if (Numbers.lastInputIsConstant(display) || + Character.isDigit(display.charAt(display.length() - 1))) { + equation += "*" + lastAns; + display += "×Ans"; + } else { + equation += lastAns; + display += "Ans"; + } + outputEq.setText(display); + } + }); + + // if parentheses were not closed properly, close them + // else if last input was operator, display error message + // else parse equation for answer + equal.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (count > 0) { + for (; count > 0; count--) { + equation += ")"; + } + } + if (equation.equals("")) + outputEq.setText(""); + else { + try { + BigDecimal answer = new Expression(equation).eval(); + + lastAns = String.valueOf(answer.doubleValue()); + + // returns long if answer is a whole number, otherwise return double + if (lastAns.substring(lastAns.length() - 2).equals(".0")) + lastAns = String.valueOf(answer.longValue()); + } catch (Exception e) { + equation = ""; + currNum = ""; + lastNum = ""; + display = ""; + outputEq.setText("Error"); + } + + lastNum = ""; + currNum = ""; + equation = ""; + display = ""; + outputAns.setText(lastAns); + } + } + }); + + // Scientific calculator + if (log != null) { + + // Toggle radians/degrees + ToggleButton toggle = (ToggleButton) findViewById(R.id.btn_rad); + toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + Toast.makeText(MainActivity.this, "Radians mode on", Toast.LENGTH_SHORT).show(); + degMode = false; + } else { + Toast.makeText(MainActivity.this, "Degrees mode on", Toast.LENGTH_SHORT).show(); + degMode = true; + } + } + }); + + // if equation if empty, ignore + // else last operator was an operator, ignore + // else try factorial calculation, catch error + factorial.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) ; + else if (Numbers.lastInputIsOperator(equation)) ; + else { + try { + equation = equation.substring(0, equation.length() - currNum.length()); + currNum = String.valueOf(Numbers.factorial(Integer.valueOf(currNum))); + equation += currNum; + display += "!"; + outputEq.setText(display); + } catch (Exception e) { + equation = ""; + currNum = ""; + lastNum = ""; + display = ""; + outputEq.setText("Error"); + } + } + } + }); + pi.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + currNum = String.valueOf(3.14159265359); + if (display.isEmpty()) { + equation += currNum; + display += "π"; + } else if (Numbers.lastInputIsConstant(display) || + Character.isDigit(display.charAt(display.length() - 1)) || + display.charAt(display.length() - 1) == ')') { + equation += "*" + currNum; + display += "×π"; + } else { + equation += currNum; + display += "π"; + } + outputEq.setText(display); + } + }); + e.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + currNum = String.valueOf(2.71828182846); + if (display.isEmpty()) { + equation += currNum; + display += "e"; + } else if (Numbers.lastInputIsConstant(display) || + Character.isDigit(display.charAt(display.length() - 1)) || + display.charAt(display.length() - 1) == ')') { + equation += "*" + currNum; + display += "×e"; + } else { + equation += currNum; + display += "e"; + } + outputEq.setText(display); + } + }); + + sin.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (degMode) { + if (equation.isEmpty()) { + equation += "SIN("; + display += "sin("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*SIN("; + display += "×sin("; + } else { + equation += "SIN("; + display += "sin("; + } + } else { + if (equation.isEmpty()) { + equation += "RAD(SIN("; + display += "sin("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*RAD(SIN("; + display += "×sin("; + } else { + equation += "RAD(SIN("; + display += "sin("; + } + count++; + } + count++; + outputEq.setText(display); + } + }); + cos.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (degMode) { + if (equation.isEmpty()) { + equation += "COS("; + display += "cos("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*COS("; + display += "×cos("; + } else { + equation += "COS("; + display += "cos("; + } + } else { + if (equation.isEmpty()) { + equation += "RAD(COS("; + display += "cos("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*RAD(COS("; + display += "×cos("; + } else { + equation += "RAD(COS("; + display += "cos("; + } + count++; + } + count++; + outputEq.setText(display); + } + }); + tan.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (degMode) { + if (equation.isEmpty()) { + equation += "TAN("; + display += "tan("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*TAN("; + display += "×tan("; + } else { + equation += "TAN("; + display += "tan("; + } + } else { + if (equation.isEmpty()) { + equation += "RAD(TAN("; + display += "tan("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*RAD(TAN("; + display += "×tan("; + } else { + equation += "RAD(TAN("; + display += "tan("; + } + count++; + } + count++; + outputEq.setText(display); + } + }); + ln.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) { + equation += "LOG("; + display += "ln("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*LOG("; + display += "×ln("; + } else { + equation += "LOG("; + display += "ln("; + } + count++; + outputEq.setText(display); + } + }); + log.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) { + equation += "LOG10("; + display += "log("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*LOG10("; + display += "×log("; + } else { + equation += "LOG10("; + display += "log("; + } + count++; + outputEq.setText(display); + } + }); + sqRoot.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty()) { + equation += "SQRT("; + display += "√("; + } else if (Character.isDigit(equation.charAt(equation.length() - 1)) || + equation.charAt(equation.length() - 1) == ')' || + Numbers.lastInputIsConstant(display)) { + equation += "*SQRT("; + display += "×√("; + } else { + equation += "SQRT("; + display += "√("; + } + count++; + outputEq.setText(display); + } + }); + + exp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (display.isEmpty()) ; + else if (Character.isDigit(display.charAt(display.length() - 1))) { + equation = equation.substring(0, display.length() - currNum.length()) + "(" + currNum; + lastNum = currNum; + currNum = "E"; + equation += "*(10^"; + display += "E"; + } + } + }); + + sq.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (equation.isEmpty() || + equation.charAt(equation.length() - 1) == '(') ; + else { + lastNum = currNum; + currNum = ""; + equation += "^"; + display += "^"; + } + outputEq.setText(display); + } + }); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("currNum", currNum); + outState.putString("equation", equation); + outState.putString("lastAns", lastAns); + outState.putString("display", display); + outState.putString("lastNum", lastNum); + outState.putInt("count", count); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } +} \ No newline at end of file diff --git a/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Numbers.java b/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Numbers.java new file mode 100644 index 0000000..28d22e5 --- /dev/null +++ b/ScientificCalculator/app/src/main/java/nyc/c4q/sufeiiz/scientificcalculator/Numbers.java @@ -0,0 +1,64 @@ +package nyc.c4q.sufeiiz.scientificcalculator; + +import android.view.View; +import android.widget.TextView; + +import java.util.Arrays; + +/** + * Created by sufeizhao on 5/5/15. + */ +public class Numbers extends MainActivity{ + + // returns String of the percentage as a decimal + public static String percentage(String num, String lastNum) { + String converted; + double newCurr = (Double.valueOf(num) / 100); + + if (lastNum.isEmpty()) { + converted = String.valueOf(newCurr); + } else { + newCurr = Double.valueOf(lastNum) * newCurr; + converted = String.valueOf(newCurr); + } + + return converted; + } + + // returns true if last input was +, -, *, /, ( + public static boolean lastInputIsOperator(String equation) { + String[] operators = {"+", "-", "*", "/", "("}; + return Arrays.asList(operators).contains(String.valueOf(equation.charAt(equation.length() - 1))); + } + + // returns true if last input was % + public static boolean lastInputIsPercent(String display) { + return display.charAt(display.length() - 1) == '%'; + } + + public static boolean lastInputIsConstant(String display) { + return display.charAt(display.length() - 1) == 's' || + display.charAt(display.length() - 1) == 'π' || + display.charAt(display.length() - 1) == 'e'; + } + + // returns factorial answer + static boolean negative = false; + public static double factorial(int x) { + if (x < 0) { + x *= -1; + negative = true; + } + if (x == 1) { + if (negative) { + return -1; + } else { + return 1; + } + } else if (x == 0) { + return 1; + } else { + return x * factorial(x-1); + } + } +} \ No newline at end of file diff --git a/ScientificCalculator/app/src/main/res/drawable/box.xml b/ScientificCalculator/app/src/main/res/drawable/box.xml new file mode 100644 index 0000000..ddee7f3 --- /dev/null +++ b/ScientificCalculator/app/src/main/res/drawable/box.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ScientificCalculator/app/src/main/res/drawable/button_bg_color.xml b/ScientificCalculator/app/src/main/res/drawable/button_bg_color.xml new file mode 100644 index 0000000..1f08fb4 --- /dev/null +++ b/ScientificCalculator/app/src/main/res/drawable/button_bg_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ScientificCalculator/app/src/main/res/drawable/button_text_color.xml b/ScientificCalculator/app/src/main/res/drawable/button_text_color.xml new file mode 100644 index 0000000..99b18ac --- /dev/null +++ b/ScientificCalculator/app/src/main/res/drawable/button_text_color.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ScientificCalculator/app/src/main/res/layout-land/activity_main.xml b/ScientificCalculator/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..e7eed11 --- /dev/null +++ b/ScientificCalculator/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +