Python 将浮点数舍入到最接近的 0.05 或另一个浮点数的倍数
Python round a float to nearest 0.05 or to multiple of another float
我想模仿 this function。我想将浮点数舍入到最接近的 0.05 的倍数(或者通常是最接近的倍数)。
我想要这个:
>>> round_nearest(1.29, 0.05)
1.25
>>> round_nearest(1.30, 0.05)
1.30
我能做到:
import math
def round_nearest(n, r):
return n - math.fmod(n, r)
>>> round_nearest(1.27, 0.05)
1.25 # Correct!
>>> round_nearest(1.30, 0.05)
1.25 # Incorrect! Correct would be 1.30.
上面的错误答案可能是由于浮点数舍入。我可以进行一些特殊情况检查,看看 n
是否“足够接近” r
的倍数而不做减法,这可能会起作用,但有没有更好的方法?还是 this strategy 是最佳选择?
您可以像这样向下舍入到最接近 a
的倍数:
def round_down(x, a):
return math.floor(x / a) * a
您可以像这样四舍五入到最接近 a
的倍数:
def round_nearest(x, a):
return round(x / a) * a
如 所写:
You can round to the nearest multiple of a like this:
def round_nearest(x, a):
return round(x / a) * a
几乎完美,但 round_nearest(1.39, 0.05)
给出 1.4000000000000001。
为避免这种情况,我建议您这样做:
import math
def round_nearest2(x, a):
return round(round(x / a) * a, -int(math.floor(math.log10(a))))
舍入到精度 a
,然后舍入到有效位数,即您的精度 a
编辑
正如@Asclepius 所示,此代码对精度的第一位数字有限制(这意味着例如,如果您输入 4.3,则四舍五入到最接近的整数,如果您输入 0.25,则数字最终四舍五入到第一位小数。这可以很容易地解决,方法是找出精度实际包含多少位数字,然后四舍五入到这个数字:
def round_nearest(x, a):
max_frac_digits = 100
for i in range(max_frac_digits):
if round(a, -int(math.floor(math.log10(a))) + i) == a:
frac_digits = -int(math.floor(math.log10(a))) + i
break
return round(round(x / a) * a, frac_digits)
frac_digits
是你精度的四舍五入 log10(最接近的数字),所以它基本上显示了应该考虑多少小数位(或者如果数字更大 - 整数位)。因此,如果您的精度为 0.25,则 frac_digits
将等于 2,因为有 2 个小数位。如果您的精度是 40,那么 frac_digits
将等于 -1,因为您需要 'go back' 小数点后的一位。
def round_nearest(x, a):
return round(round(x / a) * a, 2)
这是一个略有不同的变体。
之前的回答未通过测试 round_down(4.6, 0.2) == 4.6
。
这个答案有两种解法,不精确和精确。他们通过了所有以前的测试以及更多测试,也通过了负数。每种方法都为 round_nearest
、round_down
和 round_up
.
提供解决方案
免责声明,这些解决方案需要更多的测试。在使用 math.isclose
的地方,应用其默认公差。
你能找到一个失败的例子吗?
要设计额外的精确解,请考虑 this reference。
使用 round
(不准确)
import math
def round_nearest(num: float, to: float) -> float:
return round(num / to) * to # Credited to Paul H.
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
# Tests:
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)
使用 math.fmod
(不准确)
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
# Tests:
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
使用 math.remainder
(不准确)
此部分仅实现 round_nearest
。对于 round_down
和 round_up
,使用与“使用 round
”部分完全相同的逻辑。
def round_nearest(num: float, to: float) -> float:
return num - math.remainder(num, to)
# Tests:
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
使用 decimal.Decimal
(精确)
请注意,这是一个低效的解决方案,因为它使用了 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)
# Tests:
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)
使用 fractions.Fraction
(精确)
请注意,这是一个低效的解决方案,因为它使用了 str
。其测试结果与“Using 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)
我想模仿 this function。我想将浮点数舍入到最接近的 0.05 的倍数(或者通常是最接近的倍数)。
我想要这个:
>>> round_nearest(1.29, 0.05)
1.25
>>> round_nearest(1.30, 0.05)
1.30
我能做到:
import math
def round_nearest(n, r):
return n - math.fmod(n, r)
>>> round_nearest(1.27, 0.05)
1.25 # Correct!
>>> round_nearest(1.30, 0.05)
1.25 # Incorrect! Correct would be 1.30.
上面的错误答案可能是由于浮点数舍入。我可以进行一些特殊情况检查,看看 n
是否“足够接近” r
的倍数而不做减法,这可能会起作用,但有没有更好的方法?还是 this strategy 是最佳选择?
您可以像这样向下舍入到最接近 a
的倍数:
def round_down(x, a):
return math.floor(x / a) * a
您可以像这样四舍五入到最接近 a
的倍数:
def round_nearest(x, a):
return round(x / a) * a
如
You can round to the nearest multiple of a like this:
def round_nearest(x, a): return round(x / a) * a
几乎完美,但 round_nearest(1.39, 0.05)
给出 1.4000000000000001。
为避免这种情况,我建议您这样做:
import math
def round_nearest2(x, a):
return round(round(x / a) * a, -int(math.floor(math.log10(a))))
舍入到精度 a
,然后舍入到有效位数,即您的精度 a
编辑
正如@Asclepius 所示,此代码对精度的第一位数字有限制(这意味着例如,如果您输入 4.3,则四舍五入到最接近的整数,如果您输入 0.25,则数字最终四舍五入到第一位小数。这可以很容易地解决,方法是找出精度实际包含多少位数字,然后四舍五入到这个数字:
def round_nearest(x, a):
max_frac_digits = 100
for i in range(max_frac_digits):
if round(a, -int(math.floor(math.log10(a))) + i) == a:
frac_digits = -int(math.floor(math.log10(a))) + i
break
return round(round(x / a) * a, frac_digits)
frac_digits
是你精度的四舍五入 log10(最接近的数字),所以它基本上显示了应该考虑多少小数位(或者如果数字更大 - 整数位)。因此,如果您的精度为 0.25,则 frac_digits
将等于 2,因为有 2 个小数位。如果您的精度是 40,那么 frac_digits
将等于 -1,因为您需要 'go back' 小数点后的一位。
def round_nearest(x, a):
return round(round(x / a) * a, 2)
这是一个略有不同的变体。
round_down(4.6, 0.2) == 4.6
。
这个答案有两种解法,不精确和精确。他们通过了所有以前的测试以及更多测试,也通过了负数。每种方法都为 round_nearest
、round_down
和 round_up
.
免责声明,这些解决方案需要更多的测试。在使用 math.isclose
的地方,应用其默认公差。
你能找到一个失败的例子吗?
要设计额外的精确解,请考虑 this reference。
使用 round
(不准确)
import math
def round_nearest(num: float, to: float) -> float:
return round(num / to) * to # Credited to Paul H.
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
# Tests:
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)
使用 math.fmod
(不准确)
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
# Tests:
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
使用 math.remainder
(不准确)
此部分仅实现 round_nearest
。对于 round_down
和 round_up
,使用与“使用 round
”部分完全相同的逻辑。
def round_nearest(num: float, to: float) -> float:
return num - math.remainder(num, to)
# Tests:
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
使用 decimal.Decimal
(精确)
请注意,这是一个低效的解决方案,因为它使用了 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)
# Tests:
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)
使用 fractions.Fraction
(精确)
请注意,这是一个低效的解决方案,因为它使用了 str
。其测试结果与“Using 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)