Created
September 24, 2011 23:16
-
-
Save diogobaeder/1239977 to your computer and use it in GitHub Desktop.
Floating-point range generator (IEEE-754-proof)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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