Refactor BigDecimalUtils

This commit is contained in:
Sad Ellie 2023-11-20 16:14:45 +03:00
parent 60e5f1f998
commit 9610b5bc38
9 changed files with 67 additions and 57 deletions

View File

@ -24,7 +24,6 @@ import java.math.RoundingMode
import kotlin.math.floor import kotlin.math.floor
import kotlin.math.log10 import kotlin.math.log10
// TODO Use everywhere
fun BigDecimal.format( fun BigDecimal.format(
scale: Int, scale: Int,
outputFormat: Int outputFormat: Int
@ -35,25 +34,43 @@ fun BigDecimal.format(
.toStringWith(outputFormat) .toStringWith(outputFormat)
} }
// TODO Move tests and mark as internal
/** /**
* Shorthand function to use correct `toString` method according to [outputFormat]. * Removes all trailing zeroes.
*/ */
fun BigDecimal.toStringWith(outputFormat: Int): String { fun BigDecimal.trimZeros(): BigDecimal {
// Setting result value using a specified OutputFormat return if (this.isEqualTo(BigDecimal.ZERO)) BigDecimal.ZERO else this.stripTrailingZeros()
return when (outputFormat) {
OutputFormat.ALLOW_ENGINEERING -> this.toString()
OutputFormat.FORCE_ENGINEERING -> this.toEngineeringString()
else -> this.toPlainString()
}
} }
/**
* Uses [compareTo], ignores scale differences.
*
* @param bd BigDecimal to which this BigDecimal is to be compared.
* @return `true` if [compareTo] returned 0
*/
fun BigDecimal.isEqualTo(bd: BigDecimal): Boolean = compareTo(bd) == 0
/**
* Uses [compareTo], ignores scale differences.
*
* @param bd BigDecimal to which this BigDecimal is to be compared.
* @return `true` if [compareTo] returned 1
*/
fun BigDecimal.isGreaterThan(bd: BigDecimal): Boolean = compareTo(bd) == 1
/**
* Uses [compareTo], ignores scale differences.
*
* @param bd BigDecimal to which this BigDecimal is to be compared.
* @return `true` if [compareTo] returned -1
*/
fun BigDecimal.isLessThan(bd: BigDecimal): Boolean = compareTo(bd) == -1
/** /**
* Sets the minimum scale that is required to get first non zero value in fractional part * Sets the minimum scale that is required to get first non zero value in fractional part
* *
* @param[prefScale] Is the preferred scale, the one which will be compared against * @param[prefScale] Is the preferred scale, the one which will be compared against
*/ */
fun BigDecimal.setMinimumRequiredScale(prefScale: Int): BigDecimal { internal fun BigDecimal.setMinimumRequiredScale(prefScale: Int): BigDecimal {
/** /**
* Here we are getting the amount of zeros in fractional part before non zero value * Here we are getting the amount of zeros in fractional part before non zero value
* For example, for 0.00000123456 we need the length of 00000 * For example, for 0.00000123456 we need the length of 00000
@ -75,10 +92,13 @@ fun BigDecimal.setMinimumRequiredScale(prefScale: Int): BigDecimal {
} }
/** /**
* Removes all trailing zeroes. * Shorthand function to use correct `toString` method according to [outputFormat].
*/ */
fun BigDecimal.trimZeros(): BigDecimal { private fun BigDecimal.toStringWith(outputFormat: Int): String {
return if (this.isEqualTo(BigDecimal.ZERO)) BigDecimal.ZERO else this.stripTrailingZeros() // Setting result value using a specified OutputFormat
return when (outputFormat) {
OutputFormat.ALLOW_ENGINEERING -> this.toString()
OutputFormat.FORCE_ENGINEERING -> this.toEngineeringString()
else -> this.toPlainString()
}
} }
fun BigDecimal.isEqualTo(bd: BigDecimal): Boolean = compareTo(bd) == 0

View File

@ -16,9 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sadellie.unitto.data.units package com.sadellie.unitto.data.common
import com.sadellie.unitto.data.common.setMinimumRequiredScale
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import java.math.BigDecimal import java.math.BigDecimal

View File

@ -19,6 +19,7 @@
package com.sadellie.unitto.data.model.unit package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.core.base.MAX_PRECISION import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
@ -35,7 +36,7 @@ data class FuelBackward(
) : DefaultUnit { ) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal { override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal {
// Avoid division by zero // Avoid division by zero
if (unitTo.basicUnit.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO if (unitTo.basicUnit.isEqualTo(BigDecimal.ZERO)) return BigDecimal.ZERO
return when (unitTo) { return when (unitTo) {
is FuelForward -> this is FuelForward -> this

View File

@ -19,6 +19,7 @@
package com.sadellie.unitto.data.model.unit package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.core.base.MAX_PRECISION import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
@ -35,7 +36,7 @@ data class FuelForward(
) : DefaultUnit { ) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal { override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal {
// Avoid division by zero // Avoid division by zero
if (unitTo.basicUnit.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO if (unitTo.basicUnit.isEqualTo(BigDecimal.ZERO)) return BigDecimal.ZERO
return when (unitTo) { return when (unitTo) {
is FuelForward -> this is FuelForward -> this

View File

@ -19,6 +19,7 @@
package com.sadellie.unitto.data.model.unit package com.sadellie.unitto.data.model.unit
import com.sadellie.unitto.core.base.MAX_PRECISION import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.UnitGroup
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
@ -35,7 +36,7 @@ data class NormalUnit(
) : DefaultUnit { ) : DefaultUnit {
override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal { override fun convert(unitTo: DefaultUnit, value: BigDecimal): BigDecimal {
// Avoid division by zero // Avoid division by zero
if (unitTo.basicUnit.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO if (unitTo.basicUnit.isEqualTo(BigDecimal.ZERO)) return BigDecimal.ZERO
return this return this
.basicUnit .basicUnit

View File

@ -20,8 +20,8 @@ package com.sadellie.unitto.data.units
import android.content.Context import android.content.Context
import androidx.room.Room import androidx.room.Room
import com.sadellie.unitto.data.common.setMinimumRequiredScale import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.data.common.trimZeros import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.database.UnittoDatabase import com.sadellie.unitto.data.database.UnittoDatabase
import com.sadellie.unitto.data.model.UnitGroup import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.unit.DefaultUnit import com.sadellie.unitto.data.model.unit.DefaultUnit
@ -42,7 +42,7 @@ class AllUnitsTest {
private var history: MutableMap<UnitGroup, Set<String>> = mutableMapOf() private var history: MutableMap<UnitGroup, Set<String>> = mutableMapOf()
private val mContext: Context = RuntimeEnvironment.getApplication().applicationContext private val mContext: Context = RuntimeEnvironment.getApplication().applicationContext
private val database = Room.inMemoryDatabaseBuilder(mContext, UnittoDatabase::class.java).build() private val database = Room.inMemoryDatabaseBuilder(mContext, UnittoDatabase::class.java).build()
private val allUnitsRepository = UnitsRepository( private val allUnitsRepository = UnitsRepositoryImpl(
unitsDao = database.unitsDao(), unitsDao = database.unitsDao(),
currencyRatesDao = database.currencyRatesDao(), currencyRatesDao = database.currencyRatesDao(),
mContext = mContext mContext = mContext
@ -550,14 +550,10 @@ class AllUnitsTest {
UnitGroup.NUMBER_BASE -> (unitFrom as NumberBaseUnit).convert((unitTo as NumberBaseUnit), value) UnitGroup.NUMBER_BASE -> (unitFrom as NumberBaseUnit).convert((unitTo as NumberBaseUnit), value)
UnitGroup.FLOW_RATE -> (unitFrom as ReverseUnit) UnitGroup.FLOW_RATE -> (unitFrom as ReverseUnit)
.convert((unitTo as DefaultUnit), BigDecimal(value)) .convert((unitTo as DefaultUnit), BigDecimal(value))
.setMinimumRequiredScale(5) .format(5, OutputFormat.PLAIN)
.trimZeros()
.toPlainString()
else -> (unitFrom as DefaultUnit) else -> (unitFrom as DefaultUnit)
.convert((unitTo as DefaultUnit), BigDecimal(value)) .convert((unitTo as DefaultUnit), BigDecimal(value))
.setMinimumRequiredScale(5) .format(5, OutputFormat.PLAIN)
.trimZeros()
.toPlainString()
} }
assertEquals("Failed at $this to $checkingId", expected, actual) assertEquals("Failed at $this to $checkingId", expected, actual)
println("PASSED: $this -> $expected == $actual") println("PASSED: $this -> $expected == $actual")

View File

@ -29,11 +29,9 @@ import com.sadellie.unitto.core.ui.common.textfield.addBracket
import com.sadellie.unitto.core.ui.common.textfield.addTokens import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.core.ui.common.textfield.getTextField import com.sadellie.unitto.core.ui.common.textfield.getTextField
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.common.isExpression import com.sadellie.unitto.data.common.isExpression
import com.sadellie.unitto.data.common.setMinimumRequiredScale
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.common.toStringWith
import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.model.repository.CalculatorHistoryRepository import com.sadellie.unitto.data.model.repository.CalculatorHistoryRepository
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -104,9 +102,7 @@ internal class CalculatorViewModel @Inject constructor(
input = ui.input.text, input = ui.input.text,
radianMode = ui.radianMode, radianMode = ui.radianMode,
) )
.setMinimumRequiredScale(ui.precision) .format(ui.precision, ui.outputFormat)
.trimZeros()
.toStringWith(ui.outputFormat)
) )
} catch (e: ExpressionException.DivideByZero) { } catch (e: ExpressionException.DivideByZero) {
CalculationResult.Empty CalculationResult.Empty
@ -198,10 +194,7 @@ internal class CalculatorViewModel @Inject constructor(
_equalClicked.update { true } _equalClicked.update { true }
val resultFormatted = result val resultFormatted = result.format(prefs.precision, prefs.outputFormat)
.setMinimumRequiredScale(prefs.precision)
.trimZeros()
.toStringWith(prefs.outputFormat)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
calculatorHistoryRepository.add( calculatorHistoryRepository.add(

View File

@ -24,6 +24,9 @@ import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.base.Token import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.isEqualTo
import com.sadellie.unitto.data.common.isGreaterThan
import com.sadellie.unitto.data.common.isLessThan
import com.sadellie.unitto.data.common.trimZeros import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.model.unit.DefaultUnit import com.sadellie.unitto.data.model.unit.DefaultUnit
import com.sadellie.unitto.data.model.unit.NumberBaseUnit import com.sadellie.unitto.data.model.unit.NumberBaseUnit
@ -62,7 +65,7 @@ internal sealed class ConverterResult {
data class Default(val value: BigDecimal) : ConverterResult() { data class Default(val value: BigDecimal) : ConverterResult() {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is Default) return false if (other !is Default) return false
return this.value.compareTo(other.value) == 0 return this.value.isEqualTo(other.value)
} }
override fun hashCode(): Int = value.hashCode() override fun hashCode(): Int = value.hashCode()
@ -90,35 +93,35 @@ internal sealed class ConverterResult {
internal fun ConverterResult.Time.format(mContext: Context, formatterSymbols: FormatterSymbols): String { internal fun ConverterResult.Time.format(mContext: Context, formatterSymbols: FormatterSymbols): String {
val result = mutableListOf<String>() val result = mutableListOf<String>()
if (day.compareTo(BigDecimal.ZERO) == 1) { if (day.isGreaterThan(BigDecimal.ZERO)) {
result += "${day.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_day_short)}" result += "${day.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_day_short)}"
} }
if (hour.compareTo(BigDecimal.ZERO) == 1) { if (hour.isGreaterThan(BigDecimal.ZERO)) {
result += "${hour.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_hour_short)}" result += "${hour.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_hour_short)}"
} }
if (minute.compareTo(BigDecimal.ZERO) == 1) { if (minute.isGreaterThan(BigDecimal.ZERO)) {
result += "${minute.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_minute_short)}" result += "${minute.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_minute_short)}"
} }
if (second.compareTo(BigDecimal.ZERO) == 1) { if (second.isGreaterThan(BigDecimal.ZERO)) {
result += "${second.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_second_short)}" result += "${second.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_second_short)}"
} }
if (millisecond.compareTo(BigDecimal.ZERO) == 1) { if (millisecond.isGreaterThan(BigDecimal.ZERO)) {
result += "${millisecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_millisecond_short)}" result += "${millisecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_millisecond_short)}"
} }
if (microsecond.compareTo(BigDecimal.ZERO) == 1) { if (microsecond.isGreaterThan(BigDecimal.ZERO)) {
result += "${microsecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_microsecond_short)}" result += "${microsecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_microsecond_short)}"
} }
if (nanosecond.compareTo(BigDecimal.ZERO) == 1) { if (nanosecond.isGreaterThan(BigDecimal.ZERO)) {
result += "${nanosecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_nanosecond_short)}" result += "${nanosecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_nanosecond_short)}"
} }
if (attosecond.compareTo(BigDecimal.ZERO) == 1) { if (attosecond.isGreaterThan(BigDecimal.ZERO)) {
result += "${attosecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_attosecond_short)}" result += "${attosecond.toPlainString().formatExpression(formatterSymbols)}${mContext.getString(R.string.unit_attosecond_short)}"
} }
@ -131,7 +134,7 @@ internal fun formatTime(
val negative = input < BigDecimal.ZERO val negative = input < BigDecimal.ZERO
val inputAbs = input.abs() val inputAbs = input.abs()
if (inputAbs.compareTo(attosecondBasicUnit) == -1) return ConverterResult.Time( if (inputAbs.isLessThan(attosecondBasicUnit)) return ConverterResult.Time(
negative = negative, negative = negative,
day = BigDecimal.ZERO, day = BigDecimal.ZERO,
hour = BigDecimal.ZERO, hour = BigDecimal.ZERO,
@ -143,7 +146,7 @@ internal fun formatTime(
attosecond = inputAbs attosecond = inputAbs
) )
if (inputAbs.compareTo(nanosecondBasicUnit) == -1) return ConverterResult.Time( if (inputAbs.isLessThan(nanosecondBasicUnit)) return ConverterResult.Time(
negative = negative, negative = negative,
day = BigDecimal.ZERO, day = BigDecimal.ZERO,
hour = BigDecimal.ZERO, hour = BigDecimal.ZERO,

View File

@ -24,10 +24,8 @@ import com.sadellie.unitto.core.base.MAX_PRECISION
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.setMinimumRequiredScale import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.common.toStringWith
import com.sadellie.unitto.data.common.trimZeros
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -78,9 +76,7 @@ class FormattingViewModel @Inject constructor(
} }
return BigDecimal(bigD) return BigDecimal(bigD)
.setMinimumRequiredScale(precision) .format(precision, outputFormat)
.trimZeros()
.toStringWith(outputFormat)
.formatExpression(formatterSymbols) .formatExpression(formatterSymbols)
} }