Tại sao không có triển khai phạm vi dấu phẩy động trong Thư viện chuẩn?
Như được làm rõ bởi tất cả các bài viết ở đây, không có phiên bản dấu phẩy động của range()
. Điều đó nói rằng, các thiếu sót có ý nghĩa nếu chúng ta xem xét rằng range()
chức năng thường được sử dụng như một chỉ số (và dĩ nhiên, điều đó có nghĩa một accessor ) máy phát điện. Vì vậy, khi chúng tôi gọi range(0,40)
, chúng tôi có hiệu lực nói rằng chúng tôi muốn 40 giá trị bắt đầu từ 0, tối đa 40, nhưng không bao gồm 40 chính nó.
Khi chúng tôi xem việc tạo chỉ mục cũng nhiều về số lượng các chỉ số vì đó là giá trị của chúng, việc sử dụng triển khai float range()
trong thư viện chuẩn sẽ ít có ý nghĩa hơn. Ví dụ, nếu chúng ta gọi hàm frange(0, 10, 0.25)
, chúng ta sẽ mong đợi cả 0 và 10 được bao gồm, nhưng điều đó sẽ mang lại một vectơ với 41 giá trị.
Do đó, một frange()
chức năng tùy thuộc vào việc sử dụng nó sẽ luôn thể hiện hành vi trực quan chống lại; nó có quá nhiều giá trị theo cảm nhận từ góc độ lập chỉ mục hoặc không bao gồm một số có thể được trả về một cách hợp lý từ góc độ toán học.
Trường hợp sử dụng toán học
Như đã nói, như đã thảo luận, numpy.linspace()
thực hiện thế hệ với quan điểm toán học độc đáo:
numpy.linspace(0, 10, 41)
array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75,
2. , 2.25, 2.5 , 2.75, 3. , 3.25, 3.5 , 3.75,
4. , 4.25, 4.5 , 4.75, 5. , 5.25, 5.5 , 5.75,
6. , 6.25, 6.5 , 6.75, 7. , 7.25, 7.5 , 7.75,
8. , 8.25, 8.5 , 8.75, 9. , 9.25, 9.5 , 9.75, 10.
])
Ca sử dụng lập chỉ mục
Và đối với phối cảnh lập chỉ mục, tôi đã viết một cách tiếp cận hơi khác với một số phép thuật chuỗi phức tạp cho phép chúng tôi chỉ định số lượng vị trí thập phân.
# Float range function - string formatting method
def frange_S (start, stop, skip = 1.0, decimals = 2):
for i in range(int(start / skip), int(stop / skip)):
yield float(("%0." + str(decimals) + "f") % (i * skip))
Tương tự, chúng ta cũng có thể sử dụng hàm dựng sẵn round
và chỉ định số thập phân:
# Float range function - rounding method
def frange_R (start, stop, skip = 1.0, decimals = 2):
for i in range(int(start / skip), int(stop / skip)):
yield round(i * skip, ndigits = decimals)
So sánh nhanh & hiệu suất
Tất nhiên, với các thảo luận ở trên, các chức năng này có trường hợp sử dụng khá hạn chế. Tuy nhiên, đây là một so sánh nhanh:
def compare_methods (start, stop, skip):
string_test = frange_S(start, stop, skip)
round_test = frange_R(start, stop, skip)
for s, r in zip(string_test, round_test):
print(s, r)
compare_methods(-2, 10, 1/3)
Các kết quả giống hệt nhau cho mỗi:
-2.0 -2.0
-1.67 -1.67
-1.33 -1.33
-1.0 -1.0
-0.67 -0.67
-0.33 -0.33
0.0 0.0
...
8.0 8.0
8.33 8.33
8.67 8.67
9.0 9.0
9.33 9.33
9.67 9.67
Và một số thời gian:
>>> import timeit
>>> setup = """
... def frange_s (start, stop, skip = 1.0, decimals = 2):
... for i in range(int(start / skip), int(stop / skip)):
... yield float(("%0." + str(decimals) + "f") % (i * skip))
... def frange_r (start, stop, skip = 1.0, decimals = 2):
... for i in range(int(start / skip), int(stop / skip)):
... yield round(i * skip, ndigits = decimals)
... start, stop, skip = -1, 8, 1/3
... """
>>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000))
0.024284090992296115
>>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000))
0.025324633985292166
Có vẻ như phương pháp định dạng chuỗi thắng bằng một sợi tóc trên hệ thống của tôi.
Những hạn chế
Và cuối cùng, một minh chứng về quan điểm từ cuộc thảo luận ở trên và một hạn chế cuối cùng:
# "Missing" the last value (10.0)
for x in frange_R(0, 10, 0.25):
print(x)
0.25
0.5
0.75
1.0
...
9.0
9.25
9.5
9.75
Hơn nữa, khi skip
tham số không chia hết cho stop
giá trị, có thể có một khoảng cách ngáp cho vấn đề sau:
# Clearly we know that 10 - 9.43 is equal to 0.57
for x in frange_R(0, 10, 3/7):
print(x)
0.0
0.43
0.86
1.29
...
8.14
8.57
9.0
9.43
Có nhiều cách để giải quyết vấn đề này, nhưng vào cuối ngày, cách tiếp cận tốt nhất có lẽ là chỉ sử dụng Numpy.