Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option for fraction results #480

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions app/src/main/java/com/darkempire78/opencalculator/DecimalToFraction.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.darkempire78.opencalculator

import java.math.BigDecimal
import java.math.BigInteger
import java.math.MathContext
import java.math.RoundingMode

class DecimalToFraction {
// Recursive function to return GCD of a and b
private fun gcd(_a: BigInteger, _b: BigInteger): BigInteger {
val a = _a.abs()
val b = _b.abs()
if (a == BigInteger.ZERO) return b
else if (b == BigInteger.ZERO) return a
return if (a < b) gcd(a, b % a)
else gcd(b, a % b)
}

fun approximateRational(value: BigDecimal, tolerance: BigDecimal = BigDecimal("1E-10")): Pair<BigInteger, BigInteger> {
var r0 = value
var r1 = BigDecimal.ONE
var a0 = r0.toBigInteger()
var a1 = BigInteger.ONE
var b0 = BigInteger.ONE
var b1 = BigInteger.ZERO

var r = r0 - a0.toBigDecimal()
var n = BigInteger.ONE

while (r.abs() > tolerance) {
r = BigDecimal.ONE.divide(r, MathContext.DECIMAL128)
val ai = r.toBigInteger()

val newA = ai.multiply(a0).add(a1)
val newB = ai.multiply(b0).add(b1)

a1 = a0
b1 = b0
a0 = newA
b0 = newB

r = r.subtract(ai.toBigDecimal())

n++
}

return Pair(a0, b0)
}

fun needsApproximation(value: BigDecimal, significantFigures : Int): Boolean {
val roundedValue = value.round(MathContext(significantFigures, RoundingMode.HALF_UP))
val difference = value.subtract(roundedValue).abs()
val tolerance = BigDecimal("1E-${significantFigures+1}")

return difference > tolerance
}

fun getFraction(number: BigDecimal): String {
var intVal = number.toBigInteger() // Fetch integral value of the decimal
val fVal = number - BigDecimal(intVal) // Fetch fractional part of the decimal

if (fVal.compareTo(BigDecimal.ZERO) == 0) {
return ""
}

// Consider precision value to convert fractional part to integral equivalent
val scale = number.scale()
val pVal = BigDecimal.TEN.pow(scale)
//val pVal = BigDecimal(10000000000000)

// Check if the number needs approximation to be converted to a fraction
// Example: 1.33333...3 -> 4/3
if (needsApproximation(fVal, 10)) {
val (num, den) = approximateRational(fVal)
val isNgeative = if (intVal < BigInteger.ZERO) "-" else ""
intVal = intVal.abs()
return if (intVal == BigInteger.ZERO) {
"$isNgeative<small><sup>$num</sup>/<sub>$den</sub></small>"
} else {
"$isNgeative$intVal <small><sup>$num</sup>/<sub>$den</sub></small>"
}
}

// Calculate GCD of integral equivalent of fractional part and precision value
val gcdVal = gcd((fVal * pVal).toBigInteger(), pVal.toBigInteger())

// Calculate num and deno
val num = ((fVal * pVal / gcdVal.toBigDecimal()).toBigInteger()).abs()
val deno = ((pVal / gcdVal.toBigDecimal()).toBigInteger()).abs()

val isNgeative = if (intVal < BigInteger.ZERO) "-" else ""
intVal = intVal.abs()

return if (intVal == BigInteger.ZERO) {
//"$isNgeative <span style=\"size:$size+\">$text</span><sup>$num</sup>/<sub>$deno</sub>"
"$isNgeative<small><sup>$num</sup>/<sub>$deno</sub></small>"
} else {
"$isNgeative$intVal <small><sup>$num</sup>/<sub>$deno</sub></small>"
}
}

// Create a function that

}
54 changes: 25 additions & 29 deletions app/src/main/java/com/darkempire78/opencalculator/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.Html
import android.text.TextWatcher
import android.view.HapticFeedbackConstants
import android.view.MenuItem
Expand Down Expand Up @@ -225,31 +226,6 @@ class MainActivity : AppCompatActivity() {

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
updateResultDisplay()
/*val afterTextLength = s?.length ?: 0
// If the afterTextLength is equals to 0 we have to clear resultDisplay
if (afterTextLength == 0) {
binding.resultDisplay.setText("")
}

/* we check if the length of the text entered into the EditText
is greater than the length of the text before the change (beforeTextLength)
by more than 1 character. If it is, we assume that this is a paste event. */
val clipData = clipboardManager.primaryClip
if (clipData != null && clipData.itemCount > 0) {
//val clipText = clipData.getItemAt(0).coerceToText(this@MainActivity).toString()

if (s != null) {
//val newValue = s.subSequence(start, start + count).toString()
if (
(afterTextLength - beforeTextLength > 1)
// Removed to avoid anoying notification (https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#PastingSystemNotifications)
//|| (afterTextLength - beforeTextLength >= 1 && clipText == newValue) // Supports 1+ new caractere if it is equals to the latest element from the clipboard
) {
// Handle paste event here
updateResultDisplay()
}
}
}*/
}

override fun afterTextChanged(s: Editable?) {
Expand Down Expand Up @@ -558,6 +534,8 @@ class MainActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
if (formattedResult != calculation) {
binding.resultDisplay.text = formattedResult
} else if ("/" in binding.resultDisplay.text) {
// Pass -> Avoid updating the UI if the result is the same (fraction option ONLY)
} else {
binding.resultDisplay.text = ""
}
Expand Down Expand Up @@ -876,7 +854,7 @@ class MainActivity : AppCompatActivity() {
groupingSeparatorSymbol
)

// If result is a number and it is finite
// If there is no error -> If result is a number and it is finite
if (!(division_by_0 || domain_error || syntax_error || is_infinity || require_real_number)) {

// Remove zeros at the end of the results (after point)
Expand Down Expand Up @@ -911,9 +889,6 @@ class MainActivity : AppCompatActivity() {

// Hide the cursor (do not remove this, it's not a duplicate)
binding.input.isCursorVisible = false

// Clear resultDisplay
binding.resultDisplay.text = ""
}

if (calculation != formattedResult) {
Expand Down Expand Up @@ -964,7 +939,28 @@ class MainActivity : AppCompatActivity() {
}
}
isEqualLastAction = true

// Display the result in fraction if the option is enabled
if (MyPreferences(this@MainActivity).displayResultInFraction) {
val fractionResult = DecimalToFraction().getFraction(calculationResult)
if (fractionResult != binding.resultDisplay.text) { // Avoid updating the UI if the result is the same
withContext(Dispatchers.Main) {
// https://stackoverflow.com/questions/37904739/html-fromhtml-deprecated-in-android-n/37905107#37905107
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.resultDisplay.text = Html.fromHtml(fractionResult, Html.FROM_HTML_MODE_LEGACY)
} else {
@Suppress("DEPRECATION")
binding.resultDisplay.text = Html.fromHtml(fractionResult)
}
}
}
} else {
withContext(Dispatchers.Main) {
binding.resultDisplay.text = ""
}
}
} else {
// Display error
withContext(Dispatchers.Main) {
if (syntax_error) {
setErrorColor(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ 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_DISPLAY_RESULT_IN_FRACTION = "darkempire78.opencalculator.DISPLAY_RESULT_IN_FRACTION"
}

private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
Expand Down Expand Up @@ -58,9 +59,10 @@ class MyPreferences(context: Context) {
set(value) = preferences.edit().putBoolean(KEY_SPLIT_PARENTHESIS_BUTTON, value).apply()
var deleteHistoryOnSwipe = preferences.getBoolean(KEY_DELETE_HISTORY_ON_SWIPE, true)
set(value) = preferences.edit().putBoolean(KEY_DELETE_HISTORY_ON_SWIPE, value).apply()

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 displayResultInFraction = preferences.getBoolean(KEY_DISPLAY_RESULT_IN_FRACTION, true)
set(value) = preferences.edit().putBoolean(KEY_DISPLAY_RESULT_IN_FRACTION, value).apply()


fun getHistory(): MutableList<History> {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,6 @@
<string name="require_real_number">Real number required</string>
<string name="settings_auto_save_calculation_without_equal_button">Automatically save calculations</string>
<string name="settings_auto_save_calculation_without_equal_button_desc">Automatically save calculations in history without needing to click the equal button</string>
<string name="settings_advanced_display_fraction">Fraction</string>
<string name="settings_advanced_display_fraction_desc">Display results in fractions</string>
</resources>
10 changes: 10 additions & 0 deletions app/src/main/res/xml/root_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@
app:title="@string/settings_category_advanced"
app:color="?attr/text_color">

<SwitchPreferenceCompat
app:key="darkempire78.opencalculator.KEY_DISPLAY_RESULT_IN_FRACTION"
app:title="@string/settings_advanced_display_fraction"
app:summary="@string/settings_advanced_display_fraction_desc"
app:useSimpleSummaryProvider="true"
app:singleLineTitle="false"
app:defaultValue="true"
app:icon="@drawable/divide"
app:widgetLayout="@drawable/material_switch" />

<SwitchPreferenceCompat
app:key="darkempire78.opencalculator.LONG_CLICK_TO_COPY_VALUE"
app:title="@string/settings_advanced_long_click_copy"
Expand Down