草庐IT

Python:浮点数取整、格式化和NaN处理

Orion's Blog 2023-03-28 原文

1. 取整的三种方法

1.1 强转int类型

这种方法会直接对浮点数的小数部分进行截断(无论是正还是负)。

print(int(2.7)) # 2
print(int(-2.7)) # -2

PS: 上面所调用的int()函数实际上可视为调用int这个类的__new__()方法。事实上我还见过代码用下面这种奇技淫巧写法来创建int对象:

type = int
x = type(42)
print(x)  # 42

上面type的类型为<class 'type'>x的类型为<class 'int'>

1.2 采用math.ceil和math.floor

这种方法的取整规则如下图所示:

可以看到无论是正数还是负数,都遵循:ceil往数轴正方向取整,floor往数轴负方向取整。实例如下:

print(math.ceil(-1.27)) # -1
print(math.floor(-1.27)) # -2
print(math.ceil(1.27)) # 2
print(math.floor(1.27)) # 1

1.3 采用round

round原型为round(value, ndigits),可以将一个浮点数取整到固定的小数位。该函数对正数和负数都采取就近取整原则,而当某个值恰好等于两个整数间一半时,取整操作会取到离该值最近的那个偶数。像1.5和2.5这样的值都会取整到2。示例如下:

print(round(1.23, 0)) # 1.0
print(round(1.23, 1)) # 1.2
print(round(1.27, 1)) # 1.3
print(round(-1.27, 1)) # -1.3
print(round(1.25361, 3)) # 1.254
print(round(1.5, 0)) # 2.0
print(round(2.5, 0)) # 2.0

传递给round()参数ndigits可以是负数,这种情况下回相应取整到十位、百位、千位:

a = 1627731
print(round(a, -1)) # 1627730
print(round(a, -2)) # 1627700
print(round(a, -3)) # 1628000

2. 格式化浮点数输出

注意对值输出时别把取整和格式化操作混为一谈。如果只是将数值以固定位数输出,一般是用不着round()的,只要在用format格式化时指定所需要的精度即可(format()格式化操作会根据round()的规则进行取整,最终返回一个字符串类型)。

x = 1234.56789
s = format(x, "0.2f")
print(type(s), format(x, "0.2f")) # <class 'str'> 1234.57

除了取整到固定小数位,format()还具有许多格式化功能,如格式化输出对齐,增加千分位分隔符等。实际上面的0.2f就表示至少对齐到0个字符(相当于没有对齐操作),并保留两位小数。

小提示:.2f也表示至少对齐到0个字符(默认是0),并保留两位小数,
0.2f二者是等效的。

更多示例如下:

# 往右调整以对齐到10个字符
print(format(x, ">10.1f")) #     1234.6
# 往右调整以对齐到10个字符
print(format(x, "<10.1f")) # 1234.6    
# 居中以对齐到10个字符
print(format(x, "^10.1f")) #   1234.6  
# 增加千位分隔符
print(format(x, ",")) # 1,234.56789
# 增加千位分隔符并保存到1位小数
print(format(x, "0,.1f")) # 1,234.6

如果想使用科学计数法,只要把f改成eE即可:

print(format(x, "e")) # 1.234568e+03

print(format(x, "0.2E")) # 1.23E+03

此外,我们还可以利用字符串的translate()方法交换不同的分隔符:

swap_separators = {ord("."):",", ord(","):"."}
print(format(x, ",").translate(swap_separators)) # 1.234,56789

最后,我们这里提一下,调用字符串的.format()函数和单独调用format()函数可以达到相同的效果,如:

print("value is {:0.3f}".format(x)) # value is 1.235
print("The value is {:0,.2f}".format(x)) # The value is 1,234.57

当然我们也可以使用%操作符来对数值做格式化处理,如:

print("%.2f" % x)

print("%10.1f" % x)

print("%-10.1f" % x)

这种格式化操作虽然可行,但是比起更加现代化的format()方法,这种方法就显得不是那么强大了。如用%操作符来格式化数值时,有些功能就没法得到支持了(如添加千位分隔符)。

3. 执行精确的小数计算

我们在第一部分介绍了round()函数,我们有可能会企图用浮点取整的方式来“修正”精度上的问题,如:

a = 2.1
b = 4.2
c = a + b
print(c) # 6.300000000000001
print(c==6.3) # False
print(round(c, 2)) # 6.3 企图这样修正精度(???)

对大部分浮点数应用程序(包括科学计算与机器学习)来说,一般都不必(或者所不推荐)这么做。虽然Python的浮点运算会引入一些小误差,但这些误差实际上是底层CPU的浮点运算单元和IEEE 754浮点算数标准的一种“特性”。由于Python的浮点数类型保存的数据采用的是原始保存形式,因此只要代码中用到了float实例,那就无法避免这样的误差。

如果避免出现误差的行为非常重要(比如在金融应用中),那么可以考虑使用decimal模块。事实上在用Python做数据库库接口时经常碰到Decimal对象——当访问金融数据时尤其如此。我们通过使用Decimal对象解决上述问题:

from decimal import Decimal

a = Decimal('4.2')
b = Decimal('2.1')
print(type(a + b), a + b) # <class 'decimal.Decimal'> 6.3
print((a + b) == Decimal('6.3')) # True

这么做看起来似乎有点怪异(将数字以字符串的形式来指定)。但是Decimal对象能够以任何期望的方式来工作(支持所有常见的数学操作)。如果要将它们打印出来或者在字符串格式化函数中使用,它们看起来就和普通数字一样。它们也可以和普通intfloat类型混合操作(最后会统一强转为Decimal类型):

print(type(a + 1), a + 1) # <class 'decimal.Decimal'> 5.2

但是需要注意的是不要将其与普通float类型直接进行比较:

print((a + b) == 6.3) # False

decimal模块的强大之处在于在计算过程中灵活地控制数字的位数和四舍五入,如我们可以创建一个本地的上下文环境然后修改精度的设定,如:

from decimal import localcontext
a = Decimal("1.3")
b = Decimal("1.7")
print(a/b) # 0.7647058823529411764705882353
with localcontext() as ctx:
    ctx.prec = 3
    print(a/b) # 0.765 

with localcontext() as ctx:
    ctx.prec = 50
    print(a/b) # 0.764705882352941176470588235294117647058823529

不过还是我们上面所说的,如果我们处理的是科学或工程类型的问题,那么更常见的做法是直接使用普通的float浮点类型。首先,在真实世界中极少有东西需要计算到小数点后17位(float提供17位的精度),因此在计算中引入的微小误差不足挂齿;其次,原生的float浮点数运算性能要快许多——如果要执行大量计算,性能问题就显得很重要了。

在使用float类型时,我们同样还需要对类似相减抵消(substraction cancellation)以及把大数和小数加载一起的情况多加小心:

nums = [1.23e+18, 1, -1.23e+18]
print(sum(nums)) # 0.0

使用Decimal对象当然可以解决此问题。不过在不动用Decimal对象的情况下,我们可以使用math.fsum()以更精确的实现来解决:

import math
print(math.fsum(nums)) # 1.0

但对于其它复杂的数值算法,我们就需要研究算法本身,理解其误差传播(error propagation)了,这属于数值分析的研究范畴。在数值分析中数学家研究了大量数值算法,其中一些算法的误差处理能力优于其它算法,详情可以参见我的数值计算专栏《orion-orion:数值计算》,此处不再详述。

4. 无穷大、负无穷大和NaN的判断测试

在实际项目中我们需要对浮点数的无穷大、负无穷大或NaN(not a number)进行判断测试。在Python中没有特殊的语法来表示这些特殊的浮点值,但是它们可以通过float来创建:

a = float("inf")
b = float("-inf")
c = float("nan")
print(a, b, c) # inf -inf nan

要检查是否出现了这些值,可以使用math.isinf()math.isnan()函数:

print(math.isinf(a)) # True
print(math.isnan(c)) # True

这些特殊浮点数的详细信息可以参考IEEE 754规范。但是我们这里有几个棘手的问题需要搞清楚,尤其是设计比较操作和操作符时可能出现的问题。

无穷大值在数学计算中会进行传播,如:

a = float("inf")
print(a + 45) # inf
print(a * 10) # inf
print(10/a) # 0.0

但是,某些关于无穷大值特定的操作会导致未定义的行为并产生NaN的结果,例如:

a = float("inf")
print(a/a) # nan
b = float("-inf")
print(a + b) # nan

NaN会通过所有的操作进行传播,且不会引发任何异常,如:

c = float("nan")
print(c + 23) # nan
print(c / 2) # nan
print(c + 2) # nan

有关NaN,一个微妙的特性是他们在做比较时从不会被判定为相等,如:

c = float("nan")
d = float("nan")
print(c == d) # False
print(c is d) # False

正因为如此,唯一安全检测NaN的方法是使用math.isnan()

参考

有关Python:浮点数取整、格式化和NaN处理的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  3. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  4. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  5. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  6. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  8. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  9. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  10. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

随机推荐