1- import math
21from dataclasses import dataclass , field
2+ from math import ceil , isclose
33
44
55@dataclass
66class FloatRange :
7+ """Range of numbers that allows floating point numbers."""
8+
79 start : float | int
8- stop : float | int = None
10+ stop : float | int | None = None
911 step : float | int = 1.0
1012
1113 def __post_init__ (self ):
@@ -20,7 +22,7 @@ def __post_init__(self):
2022 raise ValueError ("'start' must be a floating point number" )
2123 if not isinstance (self .stop , float | int ):
2224 raise ValueError ("'stop' must be a floating point number" )
23- if not isinstance (self .step , float | int ) or self .step == 0 :
25+ if not isinstance (self .step , float | int ) or isclose ( self .step , 0 ) :
2426 raise ValueError ("'step' must be a non-zero floating point number" )
2527
2628 def __iter__ (self ):
@@ -35,21 +37,23 @@ def __contains__(self, element):
3537 offset = (element - self .start ) % self .step
3638 if self .step > 0 :
3739 return self .start <= element < self .stop and (
38- math . isclose (offset , 0 ) or math . isclose (offset , self .step )
40+ isclose (offset , 0 ) or isclose (offset , self .step )
3941 )
4042 else :
4143 return self .stop < element <= self .start and (
42- math . isclose (offset , 0 ) or math . isclose (offset , self .step )
44+ isclose (offset , 0 ) or isclose (offset , self .step )
4345 )
4446
4547 def __len__ (self ):
4648 """Calculate the number of elements in the range."""
4749 if any (
48- self .step > 0 and self .stop <= self .start ,
49- self .step < 0 and self .stop >= self .start ,
50+ [
51+ self .step > 0 and self .stop <= self .start ,
52+ self .step < 0 and self .stop >= self .start ,
53+ ]
5054 ):
5155 return 0
52- return math . ceil ((self .stop - self .start ) / self .step )
56+ return ceil ((self .stop - self .start ) / self .step )
5357
5458 def __getitem__ (self , index ):
5559 """Get an element in the range based on its index."""
@@ -58,7 +62,11 @@ def __getitem__(self, index):
5862 return self .start + index * self .step
5963
6064 def __reversed__ (self ):
61- """Create a FloatRange with elements in the reverse order."""
65+ """Create a FloatRange with elements in the reverse order.
66+
67+ Any number 0 < x < self.step can be used as offset. Use 0.1 when
68+ possible as an "esthetically nice" offset.
69+ """
6270 cls = type (self )
6371 offset = (1 if self .step > 0 else - 1 ) * min (0.1 , abs (self .step ) / 2 )
6472 return cls (
@@ -80,17 +88,25 @@ def index(self, element):
8088
8189@dataclass
8290class _FloatRangeIterator :
83- start : int
84- stop : int
85- step : int
91+ """Non-public iterator. Should only be initialized by FloatRange."""
92+
93+ start : float | int
94+ stop : float | int
95+ step : float | int
8696 _num_steps : int = field (default = 0 , init = False )
8797
98+ def __iter__ (self ):
99+ """Initialize the iterator."""
100+ return self
101+
88102 def __next__ (self ):
89103 """Calculate the next element in the iteration."""
90104 element = self .start + self ._num_steps * self .step
91105 if any (
92- self .step > 0 and element >= self .stop ,
93- self .step < 0 and element <= self .stop ,
106+ [
107+ self .step > 0 and element >= self .stop ,
108+ self .step < 0 and element <= self .stop ,
109+ ]
94110 ):
95111 raise StopIteration
96112 self ._num_steps += 1
0 commit comments