mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +02:00
Custom formatter.
NumberFormatter from java is bad. Updated tests, LGTM.
This commit is contained in:
parent
b49b706d3f
commit
cc1c94e86e
@ -21,98 +21,108 @@ package com.sadellie.unitto.screens
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
|
||||||
import com.sadellie.unitto.FirebaseHelper
|
|
||||||
import com.sadellie.unitto.data.*
|
import com.sadellie.unitto.data.*
|
||||||
import com.sadellie.unitto.data.preferences.OutputFormat
|
import com.sadellie.unitto.data.preferences.OutputFormat
|
||||||
import com.sadellie.unitto.data.preferences.Separator
|
import com.sadellie.unitto.data.preferences.Separator
|
||||||
import com.sadellie.unitto.data.units.AbstractUnit
|
import com.sadellie.unitto.data.units.AbstractUnit
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.text.NumberFormat
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
object Formatter {
|
object Formatter {
|
||||||
private var nf: NumberFormat = NumberFormat.getInstance(Locale.GERMANY)
|
private const val SPACE = " "
|
||||||
|
private const val PERIOD = "."
|
||||||
|
private const val COMMA = ","
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently used symbol to separate fractional part
|
* Grouping separator.
|
||||||
|
*/
|
||||||
|
private var grouping: String = SPACE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fractional part separator.
|
||||||
*/
|
*/
|
||||||
var fractional = KEY_COMMA
|
var fractional = KEY_COMMA
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change current separator
|
* Change current separator to another [separator].
|
||||||
*
|
*
|
||||||
* @param separator [Separator] to change to
|
* @see [Separator]
|
||||||
*/
|
*/
|
||||||
fun setSeparator(separator: Int) {
|
fun setSeparator(separator: Int) {
|
||||||
nf = when (separator) {
|
grouping = when (separator) {
|
||||||
Separator.PERIOD -> NumberFormat.getInstance(Locale.GERMANY) // .
|
Separator.PERIOD -> PERIOD
|
||||||
Separator.COMMA -> NumberFormat.getInstance(Locale.US) // ,
|
Separator.COMMA -> COMMA
|
||||||
else -> NumberFormat.getInstance(Locale.FRANCE) //
|
else -> SPACE
|
||||||
}
|
}
|
||||||
fractional = if (separator == Separator.PERIOD) KEY_COMMA else KEY_DOT
|
fractional = if (separator == Separator.PERIOD) KEY_COMMA else KEY_DOT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom formatter function which work with big decimals and with strings ending with a dot.
|
* Format [input].
|
||||||
* Also doesn't lose any precision
|
*
|
||||||
* @param[input] The string we want to format. Will be split with dot symbol
|
* This will replace operators to their more appealing variants: divide, multiply and minus.
|
||||||
|
* Plus operator remains unchanged.
|
||||||
|
*
|
||||||
|
* Numbers will also be formatted.
|
||||||
|
*
|
||||||
|
* @see [formatNumber]
|
||||||
*/
|
*/
|
||||||
fun format(input: String): String {
|
fun format(input: String): String {
|
||||||
// NOTE: We receive input like 1234 or 1234. or 1234.5
|
// Don't do anything to engineering string.
|
||||||
// NOTICE DOTS, not COMMAS
|
if (input.contains(KEY_E)) return formatNumber(input)
|
||||||
|
|
||||||
var formattedInput = input
|
var output = input
|
||||||
|
|
||||||
val allNumbers = input.split(
|
// We may receive expressions
|
||||||
|
// Find all numbers in that expression
|
||||||
|
val allNumbers: List<String> = input.split(
|
||||||
|
KEY_MINUS,
|
||||||
|
KEY_DIVIDE,
|
||||||
KEY_PLUS,
|
KEY_PLUS,
|
||||||
KEY_MINUS_DISPLAY,
|
KEY_MULTIPLY
|
||||||
KEY_MULTIPLY_DISPLAY,
|
|
||||||
KEY_DIVIDE_DISPLAY,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun innerFormat(str: String): String {
|
allNumbers.forEach {
|
||||||
// For engineering string we only replace decimal separator
|
output = output.replace(it, formatNumber(it))
|
||||||
if (str.contains(KEY_E)) return str.replace(KEY_DOT, fractional)
|
}
|
||||||
|
|
||||||
return try {
|
return output.replace(KEY_MINUS, KEY_MINUS_DISPLAY)
|
||||||
var result = String()
|
.replace(KEY_DIVIDE, KEY_DIVIDE_DISPLAY)
|
||||||
// Formatting everything before fractional part
|
.replace(KEY_MULTIPLY, KEY_MULTIPLY_DISPLAY)
|
||||||
result += nf.format(str.substringBefore(KEY_DOT).toBigInteger())
|
|
||||||
// Now we add the part after dot
|
|
||||||
if (str.contains(KEY_DOT)) {
|
|
||||||
result += fractional + str.substringAfter(KEY_DOT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When user input is like "+123" (with plus) it gets lost after formatting, but returns
|
* Format given [input].
|
||||||
* on unformattable input, i.e. "+123-23"
|
*
|
||||||
|
* Input must be a number. Will replace grouping separators and fractional part separators.
|
||||||
|
*
|
||||||
|
* @see grouping
|
||||||
|
* @see fractional
|
||||||
*/
|
*/
|
||||||
if (str.startsWith(KEY_PLUS)) {
|
private fun formatNumber(input: String): String {
|
||||||
result = KEY_PLUS + result
|
val splitInput = input.split(".")
|
||||||
}
|
var firstPart = splitInput[0]
|
||||||
result
|
|
||||||
} catch (e: NumberFormatException) {
|
// Number of empty symbols (spaces) we need to add to correctly split into chunks.
|
||||||
str
|
val offset = 3 - firstPart.length.mod(3)
|
||||||
} catch (e: Exception) {
|
var output = if (offset != 3) {
|
||||||
// Bad practise, but still
|
// We add some spaces at the begging so that last chunk has 3 symbols
|
||||||
Log.e("FormatterError", e.toString())
|
firstPart = " ".repeat(offset) + firstPart
|
||||||
FirebaseHelper().recordException(e)
|
firstPart.chunked(3).joinToString(this.grouping).drop(offset)
|
||||||
str
|
} else {
|
||||||
}
|
firstPart.chunked(3).joinToString(this.grouping)
|
||||||
}
|
}
|
||||||
|
|
||||||
allNumbers.forEach {
|
// Handling fractional part
|
||||||
formattedInput = formattedInput.replace(it, innerFormat(it))
|
if (input.contains(".")) {
|
||||||
|
output = output + fractional + splitInput.getOrElse(1) { "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
return formattedInput.replace(KEY_MINUS, KEY_MINUS_DISPLAY)
|
return output
|
||||||
.replace(KEY_DIVIDE, KEY_DIVIDE_DISPLAY)
|
|
||||||
.replace(KEY_MULTIPLY, KEY_MULTIPLY_DISPLAY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ private val formatter = Formatter
|
|||||||
private const val ENG_VALUE = "123.3E+21"
|
private const val ENG_VALUE = "123.3E+21"
|
||||||
private const val COMPLETE_VALUE = "123456.789"
|
private const val COMPLETE_VALUE = "123456.789"
|
||||||
private const val INCOMPLETE_VALUE = "123456."
|
private const val INCOMPLETE_VALUE = "123456."
|
||||||
|
private const val NO_FRACTIONAL_VALUE = "123456"
|
||||||
|
private const val INCOMPLETE_EXPR = "50+123456/8*0.8-12+"
|
||||||
|
private const val COMPLETE_EXPR = "50+123456/8*0.8-12+0"
|
||||||
|
|
||||||
class FormatterTest {
|
class FormatterTest {
|
||||||
|
|
||||||
@ -34,75 +37,36 @@ class FormatterTest {
|
|||||||
fun setSeparatorSpaces() {
|
fun setSeparatorSpaces() {
|
||||||
formatter.setSeparator(Separator.SPACES)
|
formatter.setSeparator(Separator.SPACES)
|
||||||
assertEquals(".", formatter.fractional)
|
assertEquals(".", formatter.fractional)
|
||||||
|
assertEquals("123.3E+21", formatter.format(ENG_VALUE))
|
||||||
|
assertEquals("123 456.789", formatter.format(COMPLETE_VALUE))
|
||||||
|
assertEquals("123 456.", formatter.format(INCOMPLETE_VALUE))
|
||||||
|
assertEquals("123 456", formatter.format(NO_FRACTIONAL_VALUE))
|
||||||
|
assertEquals("50+123 456÷8×0.8–12+", formatter.format(INCOMPLETE_EXPR))
|
||||||
|
assertEquals("50+123 456÷8×0.8–12+0", formatter.format(COMPLETE_EXPR))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun setSeparatorComma() {
|
fun setSeparatorComma() {
|
||||||
formatter.setSeparator(Separator.COMMA)
|
formatter.setSeparator(Separator.COMMA)
|
||||||
assertEquals(".", formatter.fractional)
|
assertEquals(".", formatter.fractional)
|
||||||
|
assertEquals("123.3E+21", formatter.format(ENG_VALUE))
|
||||||
|
assertEquals("123,456.789", formatter.format(COMPLETE_VALUE))
|
||||||
|
assertEquals("123,456.", formatter.format(INCOMPLETE_VALUE))
|
||||||
|
assertEquals("123,456", formatter.format(NO_FRACTIONAL_VALUE))
|
||||||
|
assertEquals("50+123,456÷8×0.8–12+", formatter.format(INCOMPLETE_EXPR))
|
||||||
|
assertEquals("50+123,456÷8×0.8–12+0", formatter.format(COMPLETE_EXPR))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun setSeparatorPeriod() {
|
fun setSeparatorPeriod() {
|
||||||
formatter.setSeparator(Separator.PERIOD)
|
formatter.setSeparator(Separator.PERIOD)
|
||||||
assertEquals(",", formatter.fractional)
|
assertEquals(",", formatter.fractional)
|
||||||
}
|
|
||||||
|
|
||||||
// ENGINEERING
|
|
||||||
@Test
|
|
||||||
fun formatEngineeringWithSpaces() {
|
|
||||||
formatter.setSeparator(Separator.SPACES)
|
|
||||||
assertEquals("123.3E+21", formatter.format(ENG_VALUE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun formatEngineeringWithComma() {
|
|
||||||
formatter.setSeparator(Separator.COMMA)
|
|
||||||
assertEquals("123.3E+21", formatter.format(ENG_VALUE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun formatEngineeringWithPeriod() {
|
|
||||||
formatter.setSeparator(Separator.PERIOD)
|
|
||||||
assertEquals("123,3E+21", formatter.format(ENG_VALUE))
|
assertEquals("123,3E+21", formatter.format(ENG_VALUE))
|
||||||
}
|
|
||||||
|
|
||||||
// COMPLETE
|
|
||||||
@Test
|
|
||||||
fun formatCompleteWithSpaces() {
|
|
||||||
formatter.setSeparator(Separator.SPACES)
|
|
||||||
assertEquals("123 456.789", formatter.format(COMPLETE_VALUE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun formatCompleteWithComma() {
|
|
||||||
formatter.setSeparator(Separator.COMMA)
|
|
||||||
assertEquals("123,456.789", formatter.format(COMPLETE_VALUE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun formatCompleteWithPeriod() {
|
|
||||||
formatter.setSeparator(Separator.PERIOD)
|
|
||||||
assertEquals("123.456,789", formatter.format(COMPLETE_VALUE))
|
assertEquals("123.456,789", formatter.format(COMPLETE_VALUE))
|
||||||
}
|
|
||||||
|
|
||||||
// INCOMPLETE
|
|
||||||
@Test
|
|
||||||
fun formatIncompleteWithSpaces() {
|
|
||||||
formatter.setSeparator(Separator.SPACES)
|
|
||||||
assertEquals("123 456.", formatter.format(INCOMPLETE_VALUE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun formatIncompleteWithComma() {
|
|
||||||
formatter.setSeparator(Separator.COMMA)
|
|
||||||
assertEquals("123,456.", formatter.format(INCOMPLETE_VALUE))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun formatIncompleteWithPeriod() {
|
|
||||||
formatter.setSeparator(Separator.PERIOD)
|
|
||||||
assertEquals("123.456,", formatter.format(INCOMPLETE_VALUE))
|
assertEquals("123.456,", formatter.format(INCOMPLETE_VALUE))
|
||||||
|
assertEquals("123.456", formatter.format(NO_FRACTIONAL_VALUE))
|
||||||
|
assertEquals("50+123.456÷8×0,8–12+", formatter.format(INCOMPLETE_EXPR))
|
||||||
|
assertEquals("50+123.456÷8×0,8–12+0", formatter.format(COMPLETE_EXPR))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user