在 Kotlin 中舍入

Rounding in Kotlin

我有Kotlin Double,希望return Int,特别四舍五入。

166.66 -> 166

166.99 -> 167

文档中没有合适的内容。我需要介于 DOWN 和 CEILING

之间的东西

Rounding in Kotlin

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

继续

这个套路的目的是计算出您需要购买多少瓶免税威士忌,这样比正常的商业街价格节省的钱就可以有效地支付您的假期费用。

您将获得高街价格 (normPrice)、免税折扣 (discount) 和假期费用。

例如,如果一瓶正常价格为 10 英镑,而免税折扣为 10%,则每瓶可节省 1 英镑。如果您的假期花费 500 英镑,那么您应该 return 的答案是 500。

所有输入均为整数。请return一个整数。向下舍入。

测试在这里:

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

我的解决方案是,但我不明白我应该如何正确舍入它:

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()
}

这适用什么规则?如果 166.99 -> 167 你想舍入它,但如果你想遵循的规则是 166.66 -> 166 你想截断双。 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.math/truncate.html

四舍五入通常会得到最接近的整数 - 166.66166.99 都会四舍五入到 170,因为它们都高于 166.5。这是正确的(也是预期的!)行为。但是 kata 告诉你向下舍入,所以用 toInt() 截断应该没问题 - 所以这两个最终都是 166.

您 运行 遇到的问题是 floating-point 舍入错误。这是您遇到问题的那个:

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

哪个应该可以正常工作:

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

但实际上:

println(377.0 * 0.4)
>> 150.8

println(9048.0 / 150.8)
>> 59.99999999999999

有更好的解释 here,但基本上由于 floating-point 数学运算的方式,有些数字您无法准确表示,最终会失去精度。这类似于十进制的 2/3 写成 0.6666667 的方式 - 最后的 7 是一个舍入误差,60 变成 59.9999999 也是如此


您可以尝试使用 BigDecimal 以获得更高的准确性,但这有点复杂 - 您可以改为添加误差范围以补偿舍入,方法是在结果中添加一个非常小的数字:

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()
}

基本上,由于您无论如何都要向下舍入,这只会影响下一个整数下的 jussssst 数字,方法是将它们推到它上面,以便它们向下舍入到那个整数。如果调整太大,它会开始将更低的数字推到下一个整数,因此它们会在不应该的地方四舍五入

所以你必须使用一个小数字,这足以弥补那些微小的错误(你需要的确切数字称为 machine epsilon 但这是一个复杂的主题,这个只是一个“足够好”的修复,似乎适合这种情况)。当你这样做时,你所有的测试用例都会通过!