diff --git a/app/src/androidTest/java/com/darkempire78/opencalculator/FractionTests.kt b/app/src/androidTest/java/com/darkempire78/opencalculator/FractionTests.kt new file mode 100644 index 000000000..c5e86b55b --- /dev/null +++ b/app/src/androidTest/java/com/darkempire78/opencalculator/FractionTests.kt @@ -0,0 +1,48 @@ +package com.darkempire78.opencalculator + +import android.content.Context +import android.widget.TextView +import androidx.test.core.app.ApplicationProvider +import com.darkempire78.opencalculator.calculator.decimalToFraction +import org.junit.Assert.assertEquals +import org.junit.Test + +class FractionTests { + val context: Context = ApplicationProvider.getApplicationContext() + val view = TextView(context) + + @Test + fun testFraction(){ + val dec = "4.563" + decimalToFraction(dec, 1.0E-4, view) + assertEquals("4 67/119", view.text.toString()) + } + + @Test + fun testLessThanOneWithZero(){ + val dec = "0.345" + decimalToFraction(dec, 1.0E-4, view) + assertEquals("69/200", view.text.toString()) + } + + @Test + fun testLessThanOneWithoutZero(){ + val dec = ".345" + decimalToFraction(dec, 1.0E-4, view) + assertEquals("69/200", view.text.toString()) + } + + @Test + fun testRepeatDecimalRounding(){ + val dec = "3.33333" + decimalToFraction(dec, 1.0E-4, view) + assertEquals("3 1/3", view.text.toString()) + } + + @Test + fun testRepeatDecimalNoRounding(){ + val dec = "3.33333" + decimalToFraction(dec, 1.0E-5, view) + assertEquals("3 33333/100000", view.text.toString()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt b/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt index 7f8239994..5e0367588 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/MyPreferences.kt @@ -28,6 +28,8 @@ class MyPreferences(context: Context) { private const val KEY_SPLIT_PARENTHESIS_BUTTON = "darkempire78.opencalculator.SPLIT_PARENTHESIS_BUTTON" private const val KEY_DELETE_HISTORY_ON_SWIPE = "darkempire78.opencalculator.DELETE_HISTORY_ELEMENT_ON_SWIPE" private const val KEY_AUTO_SAVE_CALCULATION_WITHOUT_EQUAL_BUTTON = "darkempire78.opencalculator.AUTO_SAVE_CALCULATION_WITHOUT_EQUAL_BUTTON" + private const val KEY_SHOW_FRACTION = "darkempire78.opencalculator.SHOW_FRACTION" + private const val KEY_FRACTION_PRECISION = "darkempire78.opencalculator.FRACTION_PRECISION" private const val KEY_MOVE_BACK_BUTTON_LEFT = "darkempire78.opencalculator.MOVE_BACK_BUTTON_LEFT" private const val KEY_NUMBERING_SYSTEM = "darkempire78.opencalculator.NUMBERING_SYSTEM" private const val KEY_SHOW_ON_LOCK_SCREEN = "darkempire78.opencalculator.KEY_SHOW_ON_LOCK_SCREEN" @@ -69,6 +71,10 @@ class MyPreferences(context: Context) { var autoSaveCalculationWithoutEqualButton = preferences.getBoolean(KEY_AUTO_SAVE_CALCULATION_WITHOUT_EQUAL_BUTTON, true) set(value) = preferences.edit().putBoolean(KEY_AUTO_SAVE_CALCULATION_WITHOUT_EQUAL_BUTTON, value).apply() + var showResultFraction = preferences.getBoolean(KEY_SHOW_FRACTION, false) + set(value) = preferences.edit().putBoolean(KEY_SHOW_FRACTION, value).apply() + var fractionPrecision = preferences.getString(KEY_FRACTION_PRECISION, "1.0E-4") + set(value) = preferences.edit().putString(KEY_FRACTION_PRECISION, value).apply() var moveBackButtonLeft = preferences.getBoolean(KEY_MOVE_BACK_BUTTON_LEFT, false) set(value) = preferences.edit().putBoolean(KEY_MOVE_BACK_BUTTON_LEFT, value).apply() diff --git a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt index ba09ba2a5..0553da286 100644 --- a/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt +++ b/app/src/main/java/com/darkempire78/opencalculator/activities/MainActivity.kt @@ -19,6 +19,7 @@ import android.view.accessibility.AccessibilityEvent import android.widget.Button import android.widget.HorizontalScrollView import android.widget.TableRow +import android.widget.TextView import android.widget.Toast import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity @@ -37,6 +38,7 @@ import com.darkempire78.opencalculator.R import com.darkempire78.opencalculator.TextSizeAdjuster import com.darkempire78.opencalculator.Themes import com.darkempire78.opencalculator.calculator.Calculator +import com.darkempire78.opencalculator.calculator.decimalToFraction import com.darkempire78.opencalculator.calculator.division_by_0 import com.darkempire78.opencalculator.calculator.domain_error import com.darkempire78.opencalculator.calculator.is_infinity @@ -82,6 +84,7 @@ class MainActivity : AppCompatActivity() { private var isEqualLastAction = false private var isDegreeModeActivated = true // Set degree by default private var errorStatusOld = false + private var showFraction = false // default private var isStillTheSameCalculation_autoSaveCalculationWithoutEqualOption = false private var lastHistoryElementId = "" @@ -441,6 +444,11 @@ class MainActivity : AppCompatActivity() { } } + private fun getFractionPrecision(): String { + val fractionPrecision = MyPreferences(this).fractionPrecision + return fractionPrecision.toString() + } + private fun setErrorColor(errorStatus: Boolean) { // Only run if the color needs to be updated runOnUiThread { @@ -656,8 +664,14 @@ class MainActivity : AppCompatActivity() { withContext(Dispatchers.Main) { if (formattedResult != calculation) { - binding.resultDisplay.text = formattedResult - } else { + val tView = findViewById(R.id.resultDisplay) + if (showFraction && '.' in formattedResult) { + val precision = getFractionPrecision().toDouble() + decimalToFraction(formattedResult, precision, tView) + } else { + binding.resultDisplay.text = formattedResult + } + } else if (!showFraction && !isEqualLastAction){ binding.resultDisplay.text = "" } } @@ -1051,7 +1065,14 @@ class MainActivity : AppCompatActivity() { } // Display result - withContext(Dispatchers.Main) { binding.input.setText(formattedResult) } + withContext(Dispatchers.Main) { + binding.input.setText(formattedResult) + if (showFraction && '.' in formattedResult) { + val tView = findViewById(R.id.resultDisplay) + val precision = getFractionPrecision().toDouble() + decimalToFraction(resultString, precision, tView) + } + } // Set cursor withContext(Dispatchers.Main) { @@ -1062,7 +1083,9 @@ class MainActivity : AppCompatActivity() { binding.input.isCursorVisible = false // Clear resultDisplay - binding.resultDisplay.text = "" + if (!showFraction) { + binding.resultDisplay.text = "" + } } if (calculation != formattedResult) { @@ -1328,6 +1351,10 @@ class MainActivity : AppCompatActivity() { updateInputDisplay() } + // Show result fractions if enabled + showFraction = MyPreferences(this).showResultFraction + + // Split the parentheses button (if option is enabled) if (MyPreferences(this).splitParenthesisButton) { // Hide the AC button diff --git a/app/src/main/java/com/darkempire78/opencalculator/calculator/Fraction.kt b/app/src/main/java/com/darkempire78/opencalculator/calculator/Fraction.kt new file mode 100644 index 000000000..e9fbc245a --- /dev/null +++ b/app/src/main/java/com/darkempire78/opencalculator/calculator/Fraction.kt @@ -0,0 +1,71 @@ +package com.darkempire78.opencalculator.calculator + +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.style.RelativeSizeSpan +import android.text.style.SuperscriptSpan +import android.widget.TextView +import com.darkempire78.opencalculator.calculator.parser.NumberFormatter +import kotlin.math.abs +import kotlin.math.floor + +fun decimalToFraction(calculation: String, precision: Double, textView: TextView) { + + val decimalPosition = calculation.indexOf('.') + val decimal = calculation.substring(decimalPosition).toDouble() + val whole = if (decimalPosition > 0) { + NumberFormatter.format( + calculation.substring(0, decimalPosition), + ".", + "," + ) + + } else "" + + var n1 = 1 + var n2 = 0 + var d1 = 0 + var d2 = 1 + + var dec = decimal // - base.toDouble() + val dec2 = decimal + + do { + val a = floor(dec).toInt() + val numer = n1 + val denom = d1 + n1 = a * n1 + n2 + n2 = numer + d1 = a * d1 + d2 + d2 = denom + dec = 1 / (dec - a) + } while (abs(dec2 - n1.toDouble() / d1) > dec2 * precision) + + if (whole !in " 0") { + if (n1 == 0) { + textView.text = whole + } else { + val tempString = "$whole $n1/$d1" + val stringSpan = SpannableStringBuilder(tempString) + val spaceLoc = stringSpan.indexOf(' ') + val stringLen = stringSpan.length + stringSpan.setSpan( + SuperscriptSpan(), + spaceLoc, + stringLen, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + stringSpan.setSpan( + RelativeSizeSpan(0.6f), + spaceLoc, + stringLen, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + textView.text = stringSpan + } + } else { + val tempString = "$n1/$d1" + val stringSpan = SpannableStringBuilder(tempString) + textView.text = stringSpan + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/fracprec.xml b/app/src/main/res/drawable/fracprec.xml new file mode 100644 index 000000000..d0f59a381 --- /dev/null +++ b/app/src/main/res/drawable/fracprec.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ftfrac.xml b/app/src/main/res/drawable/ftfrac.xml new file mode 100644 index 000000000..0f46fe6d2 --- /dev/null +++ b/app/src/main/res/drawable/ftfrac.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index e8c2b47b4..bffdfd981 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -86,4 +86,28 @@ 1000 + + 2 + 3 + 4 (default) + 5 + 6 + 7 + 8 + 9 + 10 + + + + 1.0E-2 + 1.0E-3 + 1.0E-4 + 1.0E-5 + 1.0E-6 + 1.0E-7 + 1.0E-8 + 1.0E-9 + 1.0E-10 + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17b95fb60..99ed5991d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,6 +84,7 @@ FORMATTING HISTORY ADVANCED + FRACTIONS Language @@ -122,6 +123,11 @@ Keep device awake Prevent device from sleeping while the app is in the foreground + Show Fractions + Display results as fractions as well as decimals + Fraction precision + How many decimal places before rounding occurs. Anything over will result in fraction rounding + HELP US SOCIAL diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 12d569ca1..9409ad5cd 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -111,6 +111,25 @@ + + + + +