https://kotlinlang.org logo
Title
c

Colton Idle

12/04/2021, 8:05 PM
I'm trying to convert a long amount in euro cents to a readable value. I'm not European and I see conflicting information on this. Here is my ext func
fun Long.euroCentsToString(): String {
    val format = NumberFormat.getCurrencyInstance()
    format.maximumFractionDigits = 2
    format.currency = Currency.getInstance("EUR")

    return format.format(this/100)
}
If I call
849999L.euroCentsToString()
I get a result of
€8,499.00
I thought what I'd get is
€8.499,99
So Basically three issues: 1. I thought europeans use
.
to separate thousands from hundreds 2. I thought europeans use
,
to designate the start of fractional (cents) 3. I thought I'd see 99 as the cents and not 00.
t

TwoClocks

12/04/2021, 8:07 PM
My guess is it uses the local (e.g. where you are) formatting rules.
I'm pretty sure you can override that.
Also, you likely have to specific rounding rules. It probably has more than 2 digits after the decimal palce. So went you tell it to display just 2 digits, it rounds (because you didn't tell it otherwise). so .999 becomes .00
c

Colton Idle

12/04/2021, 8:11 PM
Interesting. Yeah. if I do val format = NumberFormat.getCurrencyInstance(Locale.Italy) for example then I get
8.499,00 €
the trailing euro symbol is interesting, but it proves your point.
t

TwoClocks

12/04/2021, 8:11 PM
what type is
this
?
c

Colton Idle

12/04/2021, 8:12 PM
It's an extension function. so this is referring to the Long
t

TwoClocks

12/04/2021, 8:12 PM
It's common to have the symbol after the number in some countries.
👍 1
c

Colton Idle

12/04/2021, 8:13 PM
I tried setting RoundingMode.Unnecessary, but it still seemed to turn the 99 cents into 00.
t

TwoClocks

12/04/2021, 8:14 PM
oh. yeah. so if you have 12345 in a long, and you divide it by 100 you end up with 123 So when you ask that to be printed with 2 decimal points of resolution it prints out 123.00
c

Colton Idle

12/04/2021, 8:14 PM
ah okay. so dividing it by 100 is the mistake. lemme try that
e

ephemient

12/04/2021, 8:15 PM
try BigDecimal+scale
c

Colton Idle

12/04/2021, 8:15 PM
Hm. Now I end up with 849.999,00, but the original number in cents is "849999"
t

TwoClocks

12/04/2021, 8:16 PM
so, it's expecting a # in whole units (euros, or dollars or whatever), and you're giving it a unit in cents.
so what it's printing out seems correct.
c

Colton Idle

12/04/2021, 8:19 PM
return format.format(this/100.0)
fixed it. So I needed the 100.0 not 100
t

TwoClocks

12/04/2021, 8:19 PM
yeah, that's converting it to a float (or double) before it does the divide.
e

ephemient

12/04/2021, 8:20 PM
please don't use floating point with currency. fixed point will not have any rounding issues
t

TwoClocks

12/04/2021, 8:20 PM
I'm not sure what your doing, but finical system don't use floating point math for currencys
e

ephemient

12/04/2021, 8:20 PM
hence my BigDecimal suggestion immediately previously
c

Colton Idle

12/04/2021, 8:20 PM
Yeah. My BE sends me values in cents
so that theres no floating point
I didn't know to format it, I still needed BigDecimal. I'm aware of why I would use BigDecimal in other scenarios.
But turning 599L to a string of "$5.99" for example would have never occured to me that I need a BigDecimal.
e

ephemient

12/04/2021, 8:24 PM
BigDecimal(599L.toBigInteger(), 2) == BigDecimal("5.99")
BigDecimal is exact, the scaling is done on powers of 10. it feels like overkill but there isn't another fixed point number type built in
👍 1
t

TwoClocks

12/04/2021, 8:25 PM
You can keep your number in Ints/Longs if you like. But then you have to do all the "prettyPrint" stuff by hand. mod by 100 to get the cents, and print that seperatly to the dollars... etc. But only the most latency sensitive systems do that... And since your printing it... you by definition don't care about the latency... so just use BigDecimal. It's much easier to work with.
c

Colton Idle

12/04/2021, 8:28 PM
So what @ephemient posted makes sense, but now how do I put that into a number formatter?
t

TwoClocks

12/04/2021, 8:29 PM
Same way. I think number formats will work on a BigDecimal (or BigDecimal has it's on formatter, I can't rememeber0
e

ephemient

12/04/2021, 8:29 PM
NumberFormat.getCurrencyInstance(Locale.ITALY).format(BigDecimal(849999L.toBigInteger(), 2)) == "8.499,99 €"
NumberFormat.getCurrencyInstance(Locale.forLanguageTag("it-IT-u-cu-USD")).format(BigDecimal(849999L.toBigInteger(), 2)) == "8.499,99 USD"
NumberFormat.getCurrencyInstance(Locale.forLanguageTag("en-US-u-cu-EUR")).format(BigDecimal(849999L.toBigInteger(), 2)) == "€8,499.99"
c

Colton Idle

12/04/2021, 8:29 PM
It seems like this works. Let me know if its bad
fun Long.centsToString(): String {
  val format = NumberFormat.getCurrencyInstance(Locale.ITALY)
  format.maximumFractionDigits = 2
  format.currency = Currency.getInstance("EUR")

  return format.format(BigDecimal(this).divide(BigDecimal(100.0)))
}
t

TwoClocks

12/04/2021, 8:30 PM
seems fine to me.
e

ephemient

12/04/2021, 8:30 PM
just use the constructor that lets you set the scale though?
c

Colton Idle

12/04/2021, 8:32 PM
Makes sense to me? The
toBigInteger
) was unexpected to me tho
fun Long.centsToString(): String {
  val format = NumberFormat.getCurrencyInstance(Locale.ITALY)
  format.currency = Currency.getInstance("EUR")
  return format.format(BigDecimal(this.toBigInteger(), 2))
}
Ended up with this now.
e

ephemient

12/04/2021, 8:33 PM
BigDecimal is a wrapper around BigInteger + scale (e.g. where the decimal point is)
c

Colton Idle

12/04/2021, 8:34 PM
interesting. okay. Well I have the above extension for now. Seems like it'll get the job done and is accurate (no weird floating point issues). Cheers.
e

ephemient

12/04/2021, 8:35 PM
I edited my previous example to show that you can embed currency into the locale itself, as well
👍 1