之前的答案由Paul提供,但在测试round_down(4.6, 0.2) == 4.6
时失败。
这个答案有两种解决方案,不精确的和精确的。它们通过了所有以前的测试,并添加了更多测试,也适用于负数。每个方法都为round_nearest
、round_down
和round_up
提供解决方案。
免责声明,这些解决方案需要进行更多测试。在使用math.isclose
时,其默认容限应用。
你能找到一个失败的例子吗?
要设计额外的精确解决方案,请考虑此参考文献。
import math
def round_nearest(num: float, to: float) -> float:
return round(num / to) * to
def round_down(num: float, to: float) -> float:
nearest = round_nearest(num, to)
if math.isclose(num, nearest): return num
return nearest if nearest < num else nearest - to
def round_up(num: float, to: float) -> float:
nearest = round_nearest(num, to)
if math.isclose(num, nearest): return num
return nearest if nearest > num else nearest + to
rn, rd, ru = round_nearest, round_down, round_up
> rn(1.27, 0.05), rn(1.29, 0.05), rn(1.30, 0.05), rn(1.39, 0.05)
(1.25, 1.3, 1.3, 1.4000000000000001)
> rn(-1.27, 0.05), rn(-1.29, 0.05), rn(-1.30, 0.05), rn(-1.39, 0.05)
(-1.25, -1.3, -1.3, -1.4000000000000001)
> rn(4.4, 0.2), rn(4.5, 0.2), rn(4.6, 0.2)
(4.4, 4.4, 4.6000000000000005)
> rn(-4.4, 0.2), rn(-4.5, 0.2), rn(-4.6, 0.2)
(-4.4, -4.4, -4.6000000000000005)
> rn(82, 4.3)
81.7
> rd(1.27, 0.05), rd(1.29, 0.05), rd(1.30, 0.05)
(1.25, 1.25, 1.3)
> rd(-1.27, 0.05), rd(-1.29, 0.05), rd(-1.30, 0.05)
(-1.3, -1.3, -1.3)
> rd(4.4, 0.2), rd(4.5, 0.2), rd(4.6, 0.2)
(4.4, 4.4, 4.6)
> rd(-4.4, 0.2), rd(-4.5, 0.2), rd(-4.6, 0.2)
(-4.4, -4.6000000000000005, -4.6)
> ru(1.27, 0.05), ru(1.29, 0.05), ru(1.30, 0.05)
(1.3, 1.3, 1.3)
> ru(-1.27, 0.05), ru(-1.29, 0.05), ru(-1.30, 0.05)
(-1.25, -1.25, -1.3)
> ru(4.4, 0.2), ru(4.5, 0.2), ru(4.6, 0.2)
(4.4, 4.6000000000000005, 4.6)
> ru(-4.4, 0.2), ru(-4.5, 0.2), ru(-4.6, 0.2)
(-4.4, -4.4, -4.6)
import math
def round_down(num: float, to: float) -> float:
if num < 0: return -round_up(-num, to)
mod = math.fmod(num, to)
return num if math.isclose(mod, to) else num - mod
def round_up(num: float, to: float) -> float:
if num < 0: return -round_down(-num, to)
down = round_down(num, to)
return num if num == down else down + to
def round_nearest(num: float, to: float) -> float:
down, up = round_down(num, to), round_up(num, to)
return down if ((num - down) < (up - num)) else up
rd, ru, rn = round_down, round_up, round_nearest
> rd(1.27, 0.05), rd(1.29, 0.05), rd(1.30, 0.05)
(1.25, 1.25, 1.3)
> rd(-1.27, 0.05), rd(-1.29, 0.05), rd(-1.30, 0.05)
(-1.3, -1.3, -1.3)
> rd(4.4, 0.2), rd(4.5, 0.2), rd(4.6, 0.2)
(4.4, 4.4, 4.6)
> rd(-4.4, 0.2), rd(-4.5, 0.2), rd(-4.6, 0.2)
(-4.4, -4.6000000000000005, -4.6)
> ru(1.27, 0.05), ru(1.29, 0.05), ru(1.30, 0.05)
(1.3, 1.3, 1.3)
> ru(-1.27, 0.05), ru(-1.29, 0.05), ru(-1.30, 0.05)
(-1.25, -1.25, -1.3)
> ru(4.4, 0.2), ru(4.5, 0.2), ru(4.6, 0.2)
(4.4, 4.6000000000000005, 4.6)
> ru(-4.4, 0.2), ru(-4.5, 0.2), ru(-4.6, 0.2)
(-4.4, -4.4, -4.6)
> rn(1.27, 0.05), rn(1.29, 0.05), rn(1.30, 0.05), rn(1.39, 0.05)
(1.25, 1.3, 1.3, 1.4000000000000001)
> rn(-1.27, 0.05), rn(-1.29, 0.05), rn(-1.30, 0.05), rn(-1.39, 0.05)
(-1.25, -1.3, -1.3, -1.4000000000000001)
> rn(4.4, 0.2), rn(4.5, 0.2), rn(4.6, 0.2)
(4.4, 4.4, 4.6)
> rn(-4.4, 0.2), rn(-4.5, 0.2), rn(-4.6, 0.2)
(-4.4, -4.4, -4.6)
> rn(82, 4.3)
81.7
本节仅实现了round_nearest
。对于round_down
和round_up
,请使用"使用round
"部分中完全相同的逻辑。
def round_nearest(num: float, to: float) -> float:
return num - math.remainder(num, to)
rn = round_nearest
> rn(1.27, 0.05), rn(1.29, 0.05), rn(1.30, 0.05), rn(1.39, 0.05)
(1.25, 1.3, 1.3, 1.4000000000000001)
> rn(-1.27, 0.05), rn(-1.29, 0.05), rn(-1.30, 0.05), rn(-1.39, 0.05)
(-1.25, -1.3, -1.3, -1.4000000000000001)
> rn(4.4, 0.2), rn(4.5, 0.2), rn(4.6, 0.2)
(4.4, 4.4, 4.6000000000000005)
> rn(-4.4, 0.2), rn(-4.5, 0.2), rn(-4.6, 0.2)
(-4.4, -4.4, -4.6000000000000005)
> rn(82, 4.3)
81.7
请注意,这种方法并不高效,因为它使用了 str
。
from decimal import Decimal
import math
def round_nearest(num: float, to: float) -> float:
num, to = Decimal(str(num)), Decimal(str(to))
return float(round(num / to) * to)
def round_down(num: float, to: float) -> float:
num, to = Decimal(str(num)), Decimal(str(to))
return float(math.floor(num / to) * to)
def round_up(num: float, to: float) -> float:
num, to = Decimal(str(num)), Decimal(str(to))
return float(math.ceil(num / to) * to)
rn, rd, ru = round_nearest, round_down, round_up
> rn(1.27, 0.05), rn(1.29, 0.05), rn(1.30, 0.05), rn(1.39, 0.05)
(1.25, 1.3, 1.3, 1.4)
> rn(-1.27, 0.05), rn(-1.29, 0.05), rn(-1.30, 0.05), rn(-1.39, 0.05)
(-1.25, -1.3, -1.3, -1.4)
> rn(4.4, 0.2), rn(4.5, 0.2), rn(4.6, 0.2)
(4.4, 4.4, 4.6)
> rn(-4.4, 0.2), rn(-4.5, 0.2), rn(-4.6, 0.2)
(-4.4, -4.4, -4.6)
> rn(82, 4.3)
81.7
> rd(1.27, 0.05), rd(1.29, 0.05), rd(1.30, 0.05)
(1.25, 1.25, 1.3)
> rd(-1.27, 0.05), rd(-1.29, 0.05), rd(-1.30, 0.05)
(-1.3, -1.3, -1.3)
> rd(4.4, 0.2), rd(4.5, 0.2), rd(4.6, 0.2)
(4.4, 4.4, 4.6)
> rd(-4.4, 0.2), rd(-4.5, 0.2), rd(-4.6, 0.2)
(-4.4, -4.6, -4.6)
> ru(1.27, 0.05), ru(1.29, 0.05), ru(1.30, 0.05)
(1.3, 1.3, 1.3)
> ru(-1.27, 0.05), ru(-1.29, 0.05), ru(-1.30, 0.05)
(-1.25, -1.25, -1.3)
> ru(4.4, 0.2), ru(4.5, 0.2), ru(4.6, 0.2)
(4.4, 4.6, 4.6)
> ru(-4.4, 0.2), ru(-4.5, 0.2), ru(-4.6, 0.2)
(-4.4, -4.4, -4.6)
请注意,这是一种效率低下的解决方案,因为它使用了 str
。它的测试结果与“使用 decimal.Decimal
”部分相同。在我的基准测试中,使用 Fraction
的方法比使用 Decimal
更慢。
from fractions import Fraction
import math
def round_nearest(num: float, to: float) -> float:
num, to = Fraction(str(num)), Fraction(str(to))
return float(round(num / to) * to)
def round_down(num: float, to: float) -> float:
num, to = Fraction(str(num)), Fraction(str(to))
return float(math.floor(num / to) * to)
def round_up(num: float, to: float) -> float:
num, to = Fraction(str(num)), Fraction(str(to))
return float(math.ceil(num / to) * to)
my_magical_rounding(1.29, 0.05)
返回什么?1.30
还是1.25
? - dopstar