Skip to content

Instantly share code, notes, and snippets.

@diogobaeder
Created September 24, 2011 23:16
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save diogobaeder/1239977 to your computer and use it in GitHub Desktop.
Floating-point range generator (IEEE-754-proof)
#!/usr/bin/env python
'''
This is an attempt of an alternative to
http://code.activestate.com/recipes/577068-floating-point-range/
but generating the expected floating-point elements in the range,
avoiding floating-point arithmetic problems.
Currently it works rather well, although without the original
validation steps (which seem to be overengineering to me), considering
that the Decimal initializer already has the most important validation
'''
from decimal import Decimal
import unittest
EXCLUDE_BOTH = 0
INCLUDE_START = 1
INCLUDE_END = 2
INCLUDE_BOTH = INCLUDE_START | INCLUDE_END
def out_of_range(current, end, mode, step):
if mode & INCLUDE_END and step > 0:
return current > end
if mode & INCLUDE_END and step < 0:
return current < end
elif step < 0:
return current <= end
return current >= end
def args_to_decimal(start, end, step):
if start > end and end is not None:
step = -step
if end is None:
end = Decimal(str(start))
start = Decimal(str(0.0))
else:
end = Decimal(str(end))
start = Decimal(str(start))
step = Decimal(str(step))
return start, end, step
def frange(start, end=None, step=1.0, mode=INCLUDE_START):
start, end, step = args_to_decimal(start, end, step)
current = start
if not mode & INCLUDE_START:
current += step
while True:
if out_of_range(current, end, mode, step):
raise StopIteration
yield float(current)
current += step
class FrangeTest(unittest.TestCase):
def test_generates_range_with_only_end_provided(self):
floats = frange(3.0)
self.assertEqual( list(floats), [0.0, 1.0, 2.0])
def test_generates_range_with_start_and_end(self):
floats = frange(1.0, 3.0)
self.assertEqual( list(floats), [1.0, 2.0])
def test_generates_range_with_start_end_and_step(self):
floats = frange(0.0, 0.5, 0.1)
self.assertEqual( list(floats), [0.0, 0.1, 0.2, 0.3, 0.4])
def test_generates_range_with_start_and_end_included(self):
floats = frange(0.0, 3.0, mode=INCLUDE_BOTH)
self.assertEqual( list(floats), [0.0, 1.0, 2.0, 3.0])
def test_generates_range_with_start_and_end_excluded(self):
floats = frange(0.0, 3.0, mode=EXCLUDE_BOTH)
self.assertEqual( list(floats), [1.0, 2.0])
def test_generates_range_with_only_end_included(self):
floats = frange(0.0, 3.0, mode=INCLUDE_END)
self.assertEqual( list(floats), [1.0, 2.0, 3.0])
def test_generates_range_with_negative_end(self):
floats = frange(1.0, -3.0)
self.assertEqual( list(floats), [1.0, 0.0, -1.0, -2.0])
def test_generates_range_with_negative_end_including_both(self):
floats = frange(1.0, -3.0, mode=INCLUDE_BOTH)
self.assertEqual( list(floats), [1.0, 0.0, -1.0, -2.0, -3.0])
def test_generates_range_with_negative_end_but_excluding_start(self):
floats = frange(1.0, -3.0, mode=INCLUDE_END)
self.assertEqual( list(floats), [0.0, -1.0, -2.0, -3.0])
def test_generates_range_with_challenging_float_point_arithmetics(self):
floats = frange(0.0, 2.2, 0.7)
self.assertEqual( list(floats), [0.0, 0.7, 1.4, 2.1])
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment