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
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!