Finally I did what Andy Turner suggested, rounded to 3 decimals, then to 2 and then to 1:
Answer 1:
val number:Double = 0.0449999
val number3digits:Double = String.format("%.3f", number).toDouble()
val number2digits:Double = String.format("%.2f", number3digits).toDouble()
val solution:Double = String.format("%.1f", number2digits).toDouble()
Answer 2:
val number:Double = 0.0449999
val number3digits:Double = Math.round(number * 1000.0) / 1000.0
val number2digits:Double = Math.round(number3digits * 100.0) / 100.0
val solution:Double = Math.round(number2digits * 10.0) / 10.0
Result:
0.045 → 0.05 → 0.1
Note: I know it is not how it should work but sometimes you need to round up taking into account all decimals for some special cases so maybe someone finds this useful.
Answer from Noelia on Stack Overflowjava - Round Double to 1 decimal place kotlin: from 0.044999 to 0.1 - Stack Overflow
Problem with rounding a number.
android - how to round a number in kotlin - Stack Overflow
Rounding in Kotlin - Stack Overflow
How to limit float to 2 decimal places in Kotlin?
Can you cast a float to an int?
I recently started to learn Kotlin. And for now I'm enjoying it. But I wanted to re-create some old tasks from C# because I wanted to get used to Kotlin. But one of the tasks demanded to leave 2 decimal numbers after the dot.
I wonder how can I use Math.round() in order to achive this. Because I personally don't think |"%.2f".Format(value)| it's a reliable way.The picture is me trying to figure it out. Thanks in advance.
Due to floating-point inaccuracies, 36.295f is not exactly 36.295 but rather something like 36.294998. This is the explanation for the output you received.
I believe your best option is to increase the precision in the input to start with. Use double-precision floating point i.e. lose the f for single-precision floating point.
DecimalFormat by itself cannot add precision to imprecise numbers. For example, The RoundingMode.CEILING approach offered in another answer would e.g. round 36.294 to 36.3 which might not be what you wanted.
Use this example to show you how to round:
import java.math.RoundingMode
import java.text.DecimalFormat
fun main(args: Array<String>) {
val value = 36.295f
val df = DecimalFormat("#.##")
df.roundingMode = RoundingMode.CEILING
println(df.format(value))
}
Output:
36.30
So for your code you can use:
df.format(value) as your value
Check this link for more details: Link.
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.89048 / 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!
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