Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/simplefractions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import fractions
import math
import numbers
import struct
import typing

from simplefractions._simplest_in_interval import _simplest_in_interval
Expand Down Expand Up @@ -115,6 +114,11 @@ def _interval_rounding_to(
"""
Return the interval of numbers that round to a given float.

Parameters
----------
x
A finite float.

Returns
-------
left, right : fractions.Fraction
Expand All @@ -123,22 +127,18 @@ def _interval_rounding_to(
closed : bool
True if the interval is closed at both ends, else False.
"""
if x < 0:
if x < 0.0:
left, right, closed = _interval_rounding_to(-x)
return -right, -left, closed

if x == 0:
n = struct.unpack("<Q", struct.pack("<d", 0.0))[0]
x_plus = struct.unpack("<d", struct.pack("<Q", n + 1))[0]
right = (fractions.Fraction(x) + fractions.Fraction(x_plus)) / 2
if x == 0.0:
right = fractions.Fraction(math.nextafter(0.0, math.inf)) / 2
return -right, right, True

n = struct.unpack("<Q", struct.pack("<d", x))[0]
x_plus = struct.unpack("<d", struct.pack("<Q", n + 1))[0]
x_minus = struct.unpack("<d", struct.pack("<Q", n - 1))[0]

closed = n % 2 == 0
x_plus = math.nextafter(x, math.inf)
x_minus = math.nextafter(x, 0.0)
left = (fractions.Fraction(x) + fractions.Fraction(x_minus)) / 2
closed = float(left) == x
if math.isinf(x_plus):
# Corner case where x was the largest representable finite float
right = 2 * fractions.Fraction(x) - left
Expand Down
24 changes: 24 additions & 0 deletions src/simplefractions/test/test_simplefractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,30 @@ def test_simplest_from_float(self) -> None:
with self.subTest(f=f):
self.check_simplest_from_float(f)

def test_simplest_from_float_open_closed_cases(self) -> None:
# Cases where it matters whether the interval rounding to
# the float is open or closed.

# Even floats
self.assertEqual(simplest_from_float(1e16), 10**16 - 1)
self.assertEqual(simplest_from_float(1e16 + 4), 10**16 + 3)
self.assertEqual(simplest_from_float(float(2**54 - 4)), 2**54 - 5)
self.assertEqual(simplest_from_float(float(2**54)), 2**54 - 1)
self.assertEqual(simplest_from_float(float(2**54 + 8)), 2**54 + 6)

# Odd floats
self.assertEqual(simplest_from_float(1e16 + 2), 10**16 + 2)
self.assertEqual(simplest_from_float(float(2**54 - 2)), 2**54 - 2)
self.assertEqual(simplest_from_float(float(2**54 + 4)), 2**54 + 3)

def test_simplest_from_float_powers_of_two(self) -> None:
# Powers of two that are exactly representable in IEEE 754 binary64.
TWO = fractions.Fraction(2)
for e in range(-1074, 1024):
f = TWO**e
with self.subTest(f=f):
self.check_simplest_from_float(f)

def test_simplest_from_float_special_values(self) -> None:
with self.assertRaises(ValueError):
simplest_from_float(math.inf)
Expand Down