How can I convert a double to a string using the country’s formatting and back again to a string?
For example if the number is Pi and I’m in France, I want the conversion to string to produce “3,14” with rounding of 2 decimal places and then convert “3,14” back to the rounded floating point value of 3.14 if I’m still in France but produce an error if I’m in the United States.
Likewise, if the string was “3,141” in the United states, it should parse to 3141.0, since commas in the United States are used as numeric separators.
If this isn’t possible, how much I18N floating point support is there in Kotlin and where can I read about it? Thanks.
yeah, decimal formatter helps
and one more thing - string formatting is for showing numbers or parsing numbers from user native input, not for saving numbers
in both cases you can create numberformat from current user locale and parse or format with it
if it’s not jdk - read about your locale-dependend formatting, anyway try to avoid hardcoded symbols and create your formatters from user locale settings
if you for some reasons still need to save your number as text - convert it to single invariant locale for it
Could you explain more? I have user entered floating point numbers that I need to save as text. In other words, if the user entered: “1,345.6700” in the United States, I need to convert the number to 1345.67 to process. However, if my app is closed, I have to save and restore exactly what they entered and not restore it to “1345.67”.
If they enter “1.234.56”, I convert that to NaN but still restore “1.234.56”
Is DecimalFormat from Java what I want to use? It is unclear if DecimalFormat converts both to and from strings/doubles.
import java.io.File
import java.nio.ByteBuffer
import java.text.NumberFormat
import java.util.*
fun main() {
val locale = Locale.getDefault()
val nf = NumberFormat.getInstance() // current locale formatter
val userPi = nf.format(Math.PI) // user input
println("current locale: ${locale.displayName}")
println("user input in current locale: $userPi")
val parsedPi = nf.parse(userPi).toDouble() // now you have double
println("parsed input: $parsedPi")
val binaryStorage = BinaryStorage("./storage.bin")
val textStorage = TextStorage("./storage.txt")
//you save parsed input in bin
binaryStorage.save(parsedPi)
//or in text
textStorage.save(parsedPi)
val bin = binaryStorage.load() // you have double
val txt = textStorage.load() // and here too
println("load from bin storage: $bin")
println("load from text storage: $txt")
//now you can show localized value to user
println("localized output for user: ${nf.format(bin)}")
}
interface Storage {
val filepath: String
fun save(value: Double)
fun load(): Double
}
class BinaryStorage(override val filepath: String) : Storage {
private val file = File(filepath)
override fun save(value: Double) = with(file) {
if (exists())
delete()
createNewFile()
writeBytes(value.toByteArray())
}
override fun load(): Double = file.readBytes().toDouble()
private fun Double.toByteArray() =
ByteBuffer.allocate(Long.SIZE_BYTES).putDouble(this).array()
private fun ByteArray.toDouble() =
ByteBuffer.allocate(size).put(this).apply { position(0) }.double
}
class TextStorage(override val filepath: String) : Storage {
private val file = File(filepath)
private val formatter =
NumberFormat.getInstance(Locale.ENGLISH) // invariant locale for saving
override fun save(value: Double) = with(file) {
if (exists())
delete()
createNewFile()
writeText(formatter.format(value))
}
override fun load(): Double =
formatter.parse(file.readText()).toDouble()
}
prints this:
current locale: русский (Россия)
user input in current locale: 3,142
parsed input: 3.142
load from bin storage: 3.142
load from text storage: 3.142
localized output for user: 3,142
Central idea is current-locale-independend storage, you can save double value in any format you like (for multiple values use binary/json serialization of data class), but implement your IO with current locale formatter. Don’t mix your interface i/o with storage functional - divide and rule.