Python if-else代码样式,用于减少浮点取整的代码

时间:2019-03-15 10:44:24

标签: python floating-point rounding number-formatting

是否有更短,更清晰的代码样式来解决此问题? 我正在尝试将一些float值分类为区域间文件夹。

def classify(value):   
    if value < -0.85 and value >= -0.95:
        ts_folder = r'\-0.9'
    elif value < -0.75 and value >= -0.85:
        ts_folder = r'\-0.8'
    elif value < -0.65 and value >= -0.75:
        ts_folder = r'\-0.7'    
    elif value < -0.55 and value >= -0.65:
        ts_folder = r'\-0.6'   
    elif value < -0.45 and value >= -0.55:
        ts_folder = r'\-0.5'  
    elif value < -0.35 and value >= -0.45:
        ts_folder = r'\-0.4'
    elif value < -0.25 and value >= -0.35:
        ts_folder = r'\-0.3'
    elif value < -0.15 and value >= -0.25:
        ts_folder = r'\-0.2'
    elif value < -0.05 and value >= -0.15:
        ts_folder = r'\-0.1'
    elif value < 0.05 and value >= -0.05:
        ts_folder = r'\0.0'
    elif value < 0.15 and value >= 0.05:
        ts_folder = r'\0.1'
    elif value < 0.25 and value >= 0.15:
        ts_folder = r'\0.2'
    elif value < 0.35 and value >= 0.25:
        ts_folder = r'\0.3'
    elif value < 0.45 and value >= 0.35:
        ts_folder = r'\0.4'
    elif value < 0.55 and value >= 0.45:
        ts_folder = r'\0.5'
    elif value < 0.65 and value >= 0.55:
        ts_folder = r'\0.6'
    elif value < 0.75 and value >= 0.65:
        ts_folder = r'\0.7'  
    elif value < 0.85 and value >= 0.75:
        ts_folder = r'\0.8'
    elif value < 0.95 and value >= 0.85:
        ts_folder = r'\0.9'

    return ts_folder

12 个答案:

答案 0 :(得分:44)

具体解决方案

没有真正的通用解决方案,但是在您的情况下,您可以使用以下表达式。

ts_folder = r'\{:.1f}'.format(round(value, 1))

一般解决方案

如果您实际上需要某种概括,请注意任何非线性模式都会引起麻烦。虽然,有一种方法可以缩短代码。

def classify(key, intervals):
    for lo, hi, value in intervals:
        if lo <= key < hi:
            return value
    else:
        ... # return a default value or None

# A list of tuples (lo, hi, key) which associates any value in the lo to hi interval to key
intervals = [
    (value / 10 - 0.05, value / 10 + 0.05, r'\{:.1f}'.format(value / 10))
    for value in range(-9, 10)
]

value = -0.73

ts_folder = classify(value, intervals) # r'\-0.7'

请注意,对于某些float rounding error,以上内容仍不完全安全。您可以通过手动键入intervals列表而不是使用理解来提高精度。

连续间隔

如果您的数据中的间隔是连续的,那么它们之间就没有间隙,如您的示例所示,那么我们可以使用一些优化方法。即,我们只能在列表中存储每个间隔的上限。然后,通过对它们进行排序,我们可以使用bisect进行有效的查找。

import bisect

def value_from_hi(hi):
    return r'\{:.1f}'.format(hi - 0.05)

def classify(key, boundaries):
    i = bisect.bisect_right(boundaries, key)
    if i < len(boundaries):
        return value_from_hi(boundaries[i])
    else:
        ... # return some default value

# Sorted upper bounds
boundaries = [-0.85, -0.75, -0.65, -0.55, -0.45, -0.35, -0.25, -0.15, -0.05,
              0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95]

ts_folder = classify(-0.32, boundaries) # r'\-0.3'

重要说明:之所以选择使用较高的界限和bisect_right是因为在示例中排除了较高的界限。如果排除了下限,则我们必须将其与bisect_left一起使用。

还请注意,您可能希望以某些特殊方式处理[-0.95、0.95 []范围以外的数字,并注意将其保留为bisect

答案 1 :(得分:25)

bisect模块将执行正确的查找,以便从断点列表中找到正确的bin。实际上,documentation中的示例就是这样的情况:

  

bisect()函数通常可用于对数字数据进行分类。本示例使用bisect()根据一组有序的数字断点来查找考试总计(例如)的字母等级:85及以上为'A',75..84为'B',依此类推。< / p>

>>> grades = "FEDCBA"
>>> breakpoints = [30, 44, 66, 75, 85]
>>> from bisect import bisect
>>> def grade(total):
...           return grades[bisect(breakpoints, total)]
>>> grade(66)
'C'
>>> map(grade, [33, 99, 77, 44, 12, 88])
['E', 'A', 'B', 'D', 'F', 'A']

您想要一个字符串列表,而不是用于值查找的字符串,该字符串列表用于每个值范围的确切文件夹名称。例如:

breakpoints = [-0.85, -0.75, -0.65]
folders = [r'\-0.9', r'\-0.8', r'\-0.7']
foldername = folders[bisect(breakpoints, -0.72)]

当然,如果您可以自动执行此表生成的一部分(使用round()或类似方法),

答案 2 :(得分:16)

带有这样的代码块的第一条规则之一是,始终使比较朝同一方向。因此,而不是

    elif value < -0.75 and value >= -0.85:

    elif -0.85 <= value and value < -0.75:

在这一点上,您可以观察到python允许链接比较,因此您可以编写:

    elif -0.85 <= value < -0.75:

这本身就是一种进步。另外,您可以观察到这是比较的有序列表,因此,如果添加初始比较,则只需编写

    if value < -0.95:        ts_folder = ''
    elif value < -0.85:      ts_folder = r'\-0.9'
    elif value < -0.75:      ts_folder = r'\-0.8'
    elif value < -0.65:      ts_folder = r'\-0.7'    
    elif value < -0.55:      ts_folder = r'\-0.6'   
    elif value < -0.45:      ts_folder = r'\-0.5'  
    elif value < -0.35:      ts_folder = r'\-0.4'
    elif value < -0.25:      ts_folder = r'\-0.3'
    elif value < -0.15:      ts_folder = r'\-0.2'
    elif value < -0.05:      ts_folder = r'\-0.1'
    elif value < 0.05:       ts_folder = r'\0.0'
    elif value < 0.15:       ts_folder = r'\0.1'
    elif value < 0.25:       ts_folder = r'\0.2'
    elif value < 0.35:       ts_folder = r'\0.3'
    elif value < 0.45:       ts_folder = r'\0.4'
    elif value < 0.55:       ts_folder = r'\0.5'
    elif value < 0.65:       ts_folder = r'\0.6'
    elif value < 0.75:       ts_folder = r'\0.7'  
    elif value < 0.85:       ts_folder = r'\0.8'
    elif value < 0.95:       ts_folder = r'\0.9'
    else:                    ts_folder = ''

这仍然很长,但是a)可读性更高; b)它具有处理value < -0.95 or 0.95 <= value

的明确代码

答案 3 :(得分:11)

您可以使用内置的round()

ts_folder = "\\" + str(round(value + 1e-16, 1)) # To round values like .05 to .1, not .0
if ts_folder == r"\-0.0": ts_folder = r"\0.0" 

More on round()

答案 4 :(得分:11)

所有答案都围绕四舍五入,这在这种情况下似乎很好,但仅出于论证的目的,我还要指出一种很酷的python字典用法,通常被描述为其他语言切换的替代方法(es),并允许任意值。

ranges = {
    (-0.85, -0.95): r'\-0.9',
    (-0.75, -0.85): r'\-0.8',
    (-0.65, -0.75): r'\-0.7',
    (-0.55, -0.65): r'\-0.6'
    ...
}

def classify (value):
    for (ceiling, floor), rounded_value in ranges.items():
        if floor <= value < ceiling:
            return rounded_value

输出:

>>> classify(-0.78)
\-0.8

答案 5 :(得分:5)

实际上,在Python 3中,.85将舍入到.8。根据问题,.85应该四舍五入到.9

您可以尝试以下方法吗?

round2 = lambda x, y=None: round(x+1e-15, y)
ts_folder = r'\{}'.format(str(round2(value, 1)))

输出:

>>> round2(.85, 1)
0.9
>>> round2(-.85, 1)
-0.8

答案 6 :(得分:3)

from decimal import Decimal

def classify(value):
    number = Decimal(value)
    result = "%.2f" % (number)
    return Decimal(round(float(result), 2))

答案 7 :(得分:3)

如何将其变成循环?

def classify(value):
    i = -5
    while i < 95:
        if value < (i + 10) / 100.0 and value >= i / 100.0:
            return '\\' + repr((i + 5) / 100.0)
        i += 10

这绝不是有效的方法,但是它等同于您拥有的,只是更短。

答案 8 :(得分:2)

您不需要and value >= -.85中的elif value < -0.75 and value >= -0.85:;如果该值不大于-.85,则不会到达省略号。您也可以通过让每个elif立即返回来将所有if变成def classify(value): if value < -.05: if value < -.45: if value < -.65: if value < -.85: if value < -.95: return None return r'\-0.9' if value < -.75: return r'\-0.8' return r'\-0.7' ...

在这种情况下,由于边界的间隔是固定的,因此您可以四舍五入(在常规间隔的一般情况下,您可能必须先分割然后四舍五入,例如,如果间隔是每三个单位,则您将数字除以三并四舍五入)。在一般情况下,将边界存储在树形结构中,然后对项目的去向进行二进制搜索会更快。

明确地执行二进制搜索将是这样的:

(sorted([(_-9.5)/10  for _ in range(20)]+[x]).index(x)-9)/10

尽管此代码比您的代码更难读,但相对于边界数量,它的运行时间是对数而不是线性。

如果项目数明显大于边界数,那么实际上创建项目树并插入边界可能会更快。

您还可以创建一个列表,对其进行排序,然后查看索引。例如,将{{1}}与您的函数进行比较。

答案 9 :(得分:2)

许多这些答案都提出了一种四舍五入的解决方案。不幸的是,为此目的使用舍入存在三个问题,在撰写本文时,它们都成为至少一个的牺牲品。

  • 十进制值的浮点数表示不正确。例如,浮点数0.85实际上是0.8499999999999999777955395...
  • round()使用“四舍五入到四舍五入”的关系,也称为科学或银行家的四舍五入,而不是我们许多在学校学习的算术四舍五入。这意味着0.85舍入为0.8而不是0.9,而0.25舍入为0.2而不是0.3。
  • 非常小的负浮点数(和十进制数)四舍五入为-0.0,而不是OP映射所需的0.0

这些都可以使用Decimal模块来解决,尽管不像我想要的那样精致:

from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN

def classify(value):
    number = Decimal('{:.2f}'.format(value))

    if number < 0:
        round_method = ROUND_HALF_DOWN
    else:
        round_method = ROUND_HALF_UP
    rounded_number = number.quantize(Decimal('0.1'), rounding=round_method)

    if rounded_number == 0.0:
        rounded_number = Decimal('0.0')
    return r'\{}'.format(rounded_number)

ROUND_HALF_DOWN和ROUND_HALF_UP都是必需的,因为ROUND_HALF_UP实际上将从零舍入,而不是向Infinity取整。 .quantize将十进制值舍入到第一个参数给出的位置,并允许我们指定舍入方法。

奖金:使用range()划分等分点

对于bisect解决方案,这将生成OP使用的断点:

from decimal import Decimal
breakpoints = [Decimal('{}e-2'.format(e)) for e in range(-85, 96, 10)]

答案 10 :(得分:1)

看看python中的round()函数。也许不用if就可以解决问题。

使用此功能,您可以指定需要保留的位数。 例如:

x = round(5.76543, 2)
print(x)

该代码将显示5.77

答案 11 :(得分:1)

如果您不喜欢循环,请尝试如下操作:

def classify(value): 
    endpts = [-0.95, -0.85,    -0.75,    -0.65,    -0.55,    -0.45,    -0.35,    -0.25,    -0.15,    -0.05,    0.05,    0.15,    0.25,    0.35,    0.45,    0.55,    0.65,    0.75,    0.85,    0.95] 
    ts_folder = [ r'\-0.9', r'\-0.8', r'\-0.7', r'\-0.6', r'\-0.5', r'\-0.4', r'\-0.3', r'\-0.2', r'\-0.1', r'\0.0', r'\0.1', r'\0.2', r'\0.3', r'\0.4', r'\0.5', r'\0.6', r'\0.7', r'\0.8', r'\0.9'] 
    idx = [value >= end for end in endpts].index(False) 
    if not idx:
        raise ValueError('Value outside of range')
    return ts_folder[idx-1] 

当然,循环只是列表理解中的“隐藏”。 显然,在此示例中,最好以编程方式生成endptsts_fol而不是全部写出,但您指出,在实际情况下,端点和值并不是那么简单。 / p>

如果ValueError≥0.95(因为在列表推导中找不到value)或False <-0.95(因为{{1 }}是0);在这种情况下,原始版本会引发value

您还可以保存三行,并跳过一些比较:

idx

此版本返回UnboundLocalError,而不是引发超出范围的任何值的异常。