Home > Back-end >  Rounding in Kotlin
Rounding in Kotlin

Time:04-26

I have Kotlin Double and I hope to return Int, rounded specially.

166.66 -> 166

166.99 -> 167

There is nothing suitable in Documentation. I need something between DOWN and CEILING

Rounding in Kotlin

val df = DecimalFormat("#")
df.roundingMode = RoundingMode.DOWN
println(df.format(166.99))

Continuation

The purpose of this kata is to work out just how many bottles of duty free whiskey you would have to buy such that the saving over the normal high street price would effectively cover the cost of your holiday.

You will be given the high street price (normPrice), the duty free discount (discount) and the cost of the holiday.

For example, if a bottle cost £10 normally and the discount in duty free was 10%, you would save £1 per bottle. If your holiday cost £500, the answer you should return would be 500.

All inputs will be integers. Please return an integer. Round down.

Tests is here:

assertEquals(166, dutyFree(12, 50, 1000))// I have a problem with rounding here
assertEquals(294, dutyFree(17, 10, 500))
assertEquals(357, dutyFree(24, 35, 3000))
assertEquals(60, dutyFree(377, 40, 9048))// And here
assertEquals(10, dutyFree(2479, 51, 13390))

My solution is, but I dont understand how I should round it correct:

fun dutyFree(normPrice: Int, discount:Int, hol:Int) : Int {
    
    val priseWithDiscount: (Int, Int) -> Double = { a: Int, b: Int -> a*b/100.00}
    val result = hol/priseWithDiscount.invoke(normPrice, discount)

    return result.toInt()
}

CodePudding user response:

What rule is this applying? if 166.99 -> 167 you want to round it, but if the rule you want to follow is that 166.66 -> 166 you want to truncate the double. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.math/truncate.html

CodePudding user response:

Rounding usually goes to the nearest integer - 166.66 and 166.99 would both round to 170 since they're both above 166.5. That's the correct (and expected!) behaviour. But the kata tells you to round down, so truncating with toInt() should be fine - so both of those end up as 166.

The problem you're running into is a floating-point rounding error. Here's the one you're having a problem with:

assertEquals(60, dutyFree(377, 40, 9048))

which should work fine:

  • 377 * 0.4 = 150.8
  • 9048 / 150.8 = 60

but actually:

println(377.0 * 0.4)
>> 150.8

println(9048.0 / 150.8)
>> 59.99999999999999

There's a better explanation here, but basically because of the way floating-point math works, there are certain numbers you can't represent accurately, and you end up losing precision. It's similar to how 2/3 in decimal gets written as 0.6666667 - that 7 at the end is a rounding error, and so is 60 getting turned into 59.9999999


You could try using BigDecimal for more accuracy, but that's kinda complicated - what you could do instead, is add a margin of error to compensate for the rounding, by adding a very small number to the result:

fun dutyFree(normPrice: Int, discount:Int, hol:Int) : Int {
    // I just pressed 0 a few times, there's no calculation behind this number, it's just small
    val roundingWindow = 0.000000001
    
    val priseWithDiscount: (Int, Int) -> Double = { a: Int, b: Int -> a*b/100.00}
    val result = hol/priseWithDiscount.invoke(normPrice, discount)
    
   return (result   roundingWindow).toInt()
}

Basically, since you're rounding down anyway, this will only affect numbers that are jussssst under the next integer, by nudging them above it so they get rounded down to that instead. If you make that adjustment too big, it'll start to push much lower numbers up to the next integer, so they're rounded up where they shouldn't be

So you have to use a small number, that's enough to round up those tiny errors (the exact number you need is called the machine epsilon but that's a complicated subject, this is just a "good enough" fix that seems fine for this situation). And when you do that, all your test cases pass!

  • Related