有没有一种方法可以将Python中的浮点数保留x位小数?例如:
>>> x = roundfloat(66.66666666666, 4)
66.6667
>>> x = roundfloat(1.29578293, 6)
1.295783
我已经找到了缩短/截断它们的方法(66.666666666 --> 66.6666),但无法四舍五入(66.666666666 --> 66.6667)。
我觉得有必要对Ashwini Chaudhary的答案提出一些反驳。尽管看起来,round
函数的两个参数形式可以将Python浮点数舍入到指定的小数位数,但实际上这并不是你想要的解决方案,即使你认为它是。让我解释一下...
将(Python)浮点数舍入到某个小数位数的能力是经常被要求的,但事实证明它很少是实际所需的。看似简单的答案round(x, number_of_places)
有点像一个诱人的陷阱:它看起来好像做了你想做的事情,但由于Python浮点数在内部以二进制形式存储,它正在做一些相当微妙的事情。考虑以下例子:
>>> round(52.15, 1)
52.1
对于一个对round
的天真理解,这看起来是错误的:它应该向上取整到52.2
而不是向下取整到52.1
。要理解为什么不能依赖这样的行为,需要明白尽管这看起来是一个简单的十进制到十进制操作,但它远非简单。
那么这就是上面例子中真正发生的事情(深呼吸)。我们显示了离最近的n位小数点后的十进制数字最接近的二进制浮点数的十进制表示,这个二进制浮点数是用十进制编写的数值的二进制浮点数近似值。因此,为了从原始数字文字到显示输出,底层机制已经进行了四次在二进制和十进制格式之间的转换,每个方向上进行了两次转换。将其分解(并假设 IEEE 754 binary64 格式、舍入至偶数规则和 IEEE 754 规则):
首先,将数字字面量52.15
解析并转换为Python浮点数。实际存储的数字是7339460017730355 * 2**-47
,或者52.14999999999999857891452847979962825775146484375
。
作为round
操作的第一步,在内部计算最接近存储数字的小数点后1位的十进制字符串。由于存储的数字稍低于原始值52.15
,我们最终将其向下舍入并得到一个字符串52.1
。这就解释了为什么我们得到的最终输出是52.1
而不是52.2
。
然后,在round
操作的第二步中,Python将该字符串转换回浮点数,得到最接近52.1
的二进制浮点数,现在是7332423143312589 * 2**-47
,或者52.10000000000000142108547152020037174224853515625
。
最后,在Python的交互式环境中,浮点值被显示(用十进制表示)。这涉及将二进制值转换回十进制字符串,得到52.1
作为最终输出。
在Python 2.7及更高版本中,我们有一个愉快的情况,即步骤3和4中的两次转换互相抵消了。这是由于Python选择了repr
实现,它产生的是保证正确舍入到实际浮点数的最短十进制值。选择的一个后果是,如果你从具有15个或更少有效数字的任何(不太大、不太小的)十进制字面量开始,那么相应的浮点数将显示出这些完全相同的数字:
>>> x = 15.34509809234
>>> x
15.34509809234
遗憾的是,这进一步加深了Python存储十进制值的假象。但在Python 2.6中并非如此!以下是在Python 2.6中执行的原始示例:
>>> round(52.15, 1)
52.200000000000003
我们不仅会按相反的方向四舍五入,得到的是52.2而不是52.1,而且显示的值甚至不打印为52.2!这种行为导致了许多类似“round is broken!”的Python错误跟踪器报告。但是出问题的不是round函数,而是用户的期望。(好吧,好吧,在Python 2.6中,round函数有点问题,因为它没有使用正确的舍入方法。)>>> format(66.66666666666, '.4f')
'66.6667'
>>> format(1.29578293, '.6f')
'1.295783'
即使如此,人们仍然必须了解内部二进制表示,以免被表面上的十进制中间情况的行为所惊讶。
>>> format(52.15, '.1f')
'52.1'
如果你在一个需要对十进制四舍五入方向有所关注的场景中操作(例如在某些金融环境中),你可能想要使用Decimal
类型来表示你的数字。在Decimal
类型上进行十进制四舍五入比在二进制类型上进行四舍五入更合理(同样,在二进制类型上固定位数地进行四舍五入是完全合理的)。此外,decimal
模块可以更好地控制四舍五入模式。在Python 3中,round
可以直接完成这项工作。在Python 2中,您需要使用quantize
方法。>>> Decimal('66.66666666666').quantize(Decimal('1e-4'))
Decimal('66.6667')
>>> Decimal('1.29578293').quantize(Decimal('1e-6'))
Decimal('1.295783')
在极少数情况下,round
的双参数版本确实是你想要的:也许你正在将浮点数分成大小为 0.01
的区间,而且你不太关心边界情况如何处理。然而,这些情况很少出现,并且很难仅凭这些情况来证明 round
内置函数的双参数版本的存在。
使用内置函数 round()
:
In [23]: round(66.66666666666,4)
Out[23]: 66.6667
In [24]: round(1.29578293,6)
Out[24]: 1.295783
round()
的帮助文档:
round(number[, ndigits])
-> 浮点数将一个数四舍五入到指定的小数位数(默认为0位小数)。这总是返回一个浮点数。小数位数可以是负数。
Python和Numpy中的默认舍入:
In: [round(i) for i in np.arange(10) + .5]
Out: [0, 2, 2, 4, 4, 6, 6, 8, 8, 10]
def roundint(value):
return value.apply(lambda x: int(decimal.Decimal(x).to_integral_value()))
现在您可以使用roundint(df.columnname)
来进行操作。
对于数字:
In: [int(decimal.Decimal(i).to_integral_value()) for i in np.arange(10) + .5]
Out: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
我编写了一个函数(用于 Django 项目的 DecimalField),但它也可以用于 Python 项目:
这段代码:
带有测试的代码:
def convert_decimal_to_right(value, max_digits, decimal_places, signed=True):
integer_digits = max_digits - decimal_places
max_value = float((10**integer_digits)-float(float(1)/float((10**decimal_places))))
if signed:
min_value = max_value*-1
else:
min_value = 0
if value > max_value:
value = max_value
if value < min_value:
value = min_value
return round(value, decimal_places)
value = 12.12345
nb = convert_decimal_to_right(value, 4, 2)
# nb : 12.12
value = 12.126
nb = convert_decimal_to_right(value, 4, 2)
# nb : 12.13
value = 1234.123
nb = convert_decimal_to_right(value, 4, 2)
# nb : 99.99
value = -1234.123
nb = convert_decimal_to_right(value, 4, 2)
# nb : -99.99
value = -1234.123
nb = convert_decimal_to_right(value, 4, 2, signed = False)
# nb : 0
value = 12.123
nb = convert_decimal_to_right(value, 8, 4)
# nb : 12.123
def trim_to_a_point(num, dec_point):
factor = 10**dec_point # number of points to trim
num = num*factor # multiple
num = int(num) # use the trimming of int
num = num/factor #divide by the same factor of 10s you multiplied
return num
#test
a = 14.1234567
trim_to_a_point(a, 5)
output
========
14.12345
将数字乘以10的指定次幂
使用int()方法截断小数部分
除以之前乘以的相同数字
完成!
我只是出于教育目的发布了这个,我认为它是正确的 :)
马克·迪金森的回答虽然完整,但在float(52.15)情况下无法工作。经过一些测试,这是我正在使用的解决方案:
import decimal
def value_to_decimal(value, decimal_places):
decimal.getcontext().rounding = decimal.ROUND_HALF_UP # define rounding method
return decimal.Decimal(str(float(value))).quantize(decimal.Decimal('1e-{}'.format(decimal_places)))
将'value'转换为浮点数,然后再转换为字符串非常重要,这样,'value'可以是浮点数、十进制数、整数或字符串类型!
希望这能帮到大家。