先說結(jié)論:這個問題是由于cpython的地板除運算符(//)的實現(xiàn)不是 浮點除法+floor 來實現(xiàn)而是用了(被除數(shù) - 余數(shù))/除數(shù) 導(dǎo)致的。
站在用戶的角度思考問題,與客戶深入溝通,找到濱海新區(qū)網(wǎng)站設(shè)計與濱海新區(qū)網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、國際域名空間、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋濱海新區(qū)地區(qū)。
PS:Jython下可以得到20.0,而PEP里規(guī)定了a // b應(yīng)該等于round(a/b),所以似乎這是cpython實現(xiàn)的一個bug?
首先先分析下1 / 0.05究竟應(yīng)該等于多少。答案就是精確的20.0。
簡單解釋下:IEEE754浮點數(shù)規(guī)定,如果一個浮點數(shù)的值不能被精確記錄,那么它的值會被記成與這個數(shù)距離最近的可以被IEEE浮點數(shù)表示的數(shù)。
首先,0.05在二進(jìn)制下是無限循環(huán)小數(shù),自然不能被精確記錄,因此0.05這個浮點數(shù)的實際值是不等于0.05的,實際值是約為0.05 + 2.7e-18。
之后做浮點除法,實際上做的是1 / (0.05+2.7...e-18),這個除法的結(jié)果大約是20 - 1.1e-15。這個值也不能被精確表示,恰好離這個數(shù)最近的可以表示的值就是20.0,因此即使有浮點數(shù)誤差結(jié)果也是精確的20.0。
既然1/0.05就是20.0,那么對他做floor運算自然也是20了。
現(xiàn)在的問題就是為什么1 // 0.05會變成19.0,要解決這個問題只能翻源碼看//運算符的實現(xiàn)。
直接把cpython/floatobject.c at 829b49cbd2e4b1d573470da79ca844b730120f3d · python/cpython · GitHub 中實現(xiàn)//運算的一段貼上來:
static PyObject *
float_divmod(PyObject *v, PyObject *w)
{
double vx, wx;
double div, mod, floordiv;
CONVERT_TO_DOUBLE(v, vx);
CONVERT_TO_DOUBLE(w, wx);
if (wx == 0.0) {
PyErr_SetString(PyExc_ZeroDivisionError, "float divmod()");
return NULL;
}
PyFPE_START_PROTECT("divmod", return 0)
mod = fmod(vx, wx);
/* fmod is typically exact, so vx-mod is *mathematically* an
exact multiple of wx. But this is fp arithmetic, and fp
vx - mod is an approximation; the result is that div may
not be an exact integral value after the division, although
it will always be very close to one.
*/
div = (vx - mod) / wx;
if (mod) {
/* ensure the remainder has the same sign as the denominator */
if ((wx 0) != (mod 0)) {
mod += wx;
div -= 1.0;
}
}
else {
/* the remainder is zero, and in the presence of signed zeroes
fmod returns different results across platforms; ensure
it has the same sign as the denominator. */
mod = copysign(0.0, wx);
}
/* snap quotient to nearest integral value */
if (div) {
floordiv = floor(div);
if (div - floordiv 0.5)
floordiv += 1.0;
}
else {
/* div is zero - get the same sign as the true quotient */
floordiv = copysign(0.0, vx / wx); /* zero w/ sign of vx/wx */
}
PyFPE_END_PROTECT(floordiv)
return Py_BuildValue("(dd)", floordiv, mod);
}
可以發(fā)現(xiàn)cpython中x // y的實現(xiàn)實際上是
round((x - fmod(x, y)) / y)
,其中fmod函數(shù)是求兩個浮點數(shù)相除的余數(shù)。
這樣一來就解釋的通了:在十進(jìn)制下,顯然1除以0.05的余數(shù)應(yīng)該是0.0。然而在IEEE浮點數(shù)環(huán)境中,0.05的實際值是約0.05 + 2.7e-18,略大于0.05,這樣一來1除以這個數(shù)的余數(shù)就成了約0.05 - 5e-17,從1中減掉這么多之后就只剩0.95了,除以0.05再round后變成19.0。
在 Python 交互模式下,你可以使用模運算符(%)來表示整數(shù)的余數(shù)。例如,要求 20 除以 6 的余數(shù),可以使用如下代碼:
模運算1
這里,20 除以 6 的余數(shù)是 2。
注意,模運算符(%)只能用于求整數(shù)的余數(shù),對于浮點數(shù),它是不適用的。如果要求浮點數(shù)的余數(shù),可以使用內(nèi)置函數(shù) math.fmod()。
例如:
模運算2
這里,函數(shù) math.fmod() 返回了浮點數(shù) 20 除以 6 的余數(shù) 2.0。
先來看一下 math 模塊中包含內(nèi)容,如下所示:
接下來具體看一下該模塊的常用函數(shù)和常量。
ceil(x)
返回 x 的上限,即大于或者等于 x 的最小整數(shù)??聪率纠?/p>
floor(x)
返回 x 的向下取整,小于或等于 x 的最大整數(shù)??聪率纠?/p>
fabs(x)
返回 x 的絕對值??聪率纠?/p>
fmod(x, y)
返回 x/y 的余數(shù),值為浮點數(shù)??聪率纠?/p>
factorial(x)
返回 x 的階乘,如果 x 不是整數(shù)或為負(fù)數(shù)時則將引發(fā) ValueError??聪率纠?/p>
pow(x, y)
返回 x 的 y 次冪??聪率纠?/p>
fsum(iterable)
返回迭代器中所有元素的和。看下示例:
gcd(x, y)
返回整數(shù) x 和 y 的最大公約數(shù)。看下示例:
sqrt(x)
返回 x 的平方根??聪率纠?/p>
trunc(x)
返回 x 的整數(shù)部分??聪率纠?/p>
exp(x)
返回 e 的 x 次冪??聪率纠?/p>
log(x[, base])
返回 x 的對數(shù),底數(shù)默認(rèn)為 e??聪率纠?/p>
常量
tan(x)
返回 x 弧度的正切值。看下示例:
atan(x)
返回 x 的反正切值??聪率纠?/p>
sin(x)
返回 x 弧度的正弦值??聪率纠?/p>
asin(x)
返回 x 的反正弦值??聪率纠?/p>
cos(x)
返回 x 弧度的余弦值??聪率纠?/p>
acos(x)
返回 x 的反余弦值??聪率纠?/p>
decimal 模塊為正確舍入十進(jìn)制浮點運算提供了支持,相比內(nèi)置的浮點類型 float,它能更加精確的控制精度,能夠為精度要求較高的金融等領(lǐng)域提供支持。
decimal 在一個獨立的 context 下工作,可以使用 getcontext() 查看當(dāng)前上下文,如下所示:
從上面的結(jié)果中我們可以看到 prec=28,這就是默認(rèn)的精度,我們可以使用 getcontext().prec = xxx 來重新設(shè)置精度。接下來通過具體示例看一下。
基本運算
執(zhí)行結(jié)果:
上面結(jié)果是用了默認(rèn)精度,我們重新設(shè)置下精度再來看一下:
執(zhí)行結(jié)果:
random 模塊可以生成隨機(jī)數(shù),我們來看一下其常用函數(shù)。
random()
返回 [0.0, 1.0) 范圍內(nèi)的一個隨機(jī)浮點數(shù)??聪率纠?/p>
uniform(a, b)
返回 [a, b) 范圍內(nèi)的一個隨機(jī)浮點數(shù)??聪率纠?/p>
randint(a, b)
返回 [a, b] 范圍內(nèi)的一個隨機(jī)整數(shù)??聪率纠?/p>
randrange(start, stop[, step])
返回 [start, stop) 范圍內(nèi)步長為 step 的一個隨機(jī)整數(shù)。看下示例:
choice(seq)
從非空序列 seq 返回一個隨機(jī)元素。 看下示例:
shuffle(x[, random])
將序列 x 隨機(jī)打亂位置??聪率纠?/p>
sample(population, k)
返回從總體序列或集合中選擇的唯一元素的 k 長度列表,用于無重復(fù)的隨機(jī)抽樣??聪率纠?/p>
參考: