Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the default context equal to IEEE 754 quadruple precision. #72

Merged
merged 4 commits into from
Nov 1, 2015
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
45 changes: 39 additions & 6 deletions bigfloat/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@


class Context(object):
"""
Information about output format and rounding mode.

A context provides information about the output format for a
particular operation (target precision, minimum and maximum
exponents, and whether to use gradual underflow), and the
rounding mode used to round the true result to that format.

Attributes
----------
'precision' is the precision in bits (for example, 53 for
standard IEEE double precision).
'subnormalize' is True for formats that have gradual underflow
and False otherwise.
'emin' is the minimum exponent; for example, -1073 for IEEE 754
double precision
'emax' is the maximum exponent; for example, 1024 for IEEE 754
double precision.
'rounding' is the rounding mode.

Note that exponent values are relative to a significand scaled to have
absolute value in the range [0.5, 1.0), and that ``emin`` takes
the subnormal range into account when ``subnormalize`` is ``True``.
"""
# Contexts are supposed to be immutable. We make the attributes
# of a Context private, and provide properties to access them in
# order to discourage users from trying to set the attributes
Expand Down Expand Up @@ -149,16 +173,26 @@ def __exit__(self, *args):
# some useful contexts

# DefaultContext is the context that the module always starts with.
DefaultContext = Context(precision=53,
rounding=ROUND_TIES_TO_EVEN,
emax=EMAX_MAX,
emin=EMIN_MIN,
subnormalize=False)
DefaultContext = Context(
rounding=ROUND_TIES_TO_EVEN,
precision=113,
emax=16384,
emin=-16493,
subnormalize=True,
)

# EmptyContext is useful for situations where a context is
# required, but no change to the current context is desirable
EmptyContext = Context()

# WideExponentContext has the largest exponent range allowed
# by MPFR; precision and rounding mode are not specified.
WideExponentContext = Context(
emax=EMAX_MAX,
emin=EMIN_MIN,
subnormalize=False,
)

# thread local variables:
# __bigfloat_context__: current context
# __context_stack__: context stack, used by with statement
Expand Down Expand Up @@ -237,7 +271,6 @@ def extra_precision(prec):
BigFloat.exact('0.88622692545275801364912', precision=73)

"""

c = getcontext()
return Context(precision=c.precision + prec)

Expand Down
13 changes: 9 additions & 4 deletions bigfloat/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@

from bigfloat.context import (
Context,
DefaultContext,
EmptyContext,
WideExponentContext,

RoundTiesToEven,
RoundTowardPositive,
RoundTowardNegative,

Expand Down Expand Up @@ -312,7 +313,6 @@ def exact(cls, value, precision=None):

This constructor makes no use of the current context.
"""

# figure out precision to use
if isinstance(value, six.string_types):
if precision is None:
Expand All @@ -333,10 +333,15 @@ def exact(cls, value, precision=None):
raise TypeError("Can't convert argument %s of type %s "
"to BigFloat" % (value, type(value)))

# use Default context, with given precision
# Use unlimited exponents, with given precision.
with _saved_flags():
set_flagstate(set()) # clear all flags
with DefaultContext + Context(precision=precision):
context = (
WideExponentContext +
Context(precision=precision) +
RoundTiesToEven
)
with context:
result = BigFloat(value)
if test_flag(Overflow):
raise ValueError("value too large to represent as a BigFloat")
Expand Down
20 changes: 13 additions & 7 deletions bigfloat/ieee.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with the bigfloat package. If not, see <http://www.gnu.org/licenses/>.

from bigfloat.core import log2
from bigfloat.context import Context, DefaultContext
from bigfloat.core import _bit_length
from bigfloat.context import Context


def IEEEContext(bitwidth):
Expand All @@ -37,11 +37,17 @@ def IEEEContext(bitwidth):
if not (bitwidth >= 128 and bitwidth % 32 == 0):
raise ValueError("nonstandard bitwidth: bitwidth should be "
"16, 32, 64, 128, or k*32 for some k >= 4")

with DefaultContext + Context(emin=-1, subnormalize=True):
# log2(bitwidth), rounded to the nearest quarter
l = log2(bitwidth)
precision = 13 + bitwidth - int(4 * l)
# The formula for the precision involves rounding 4*log2(width) to the
# nearest integer. We have:
#
# round(4*log2(width)) == round(log2(width**8)/2)
# == floor((log2(width**8) + 1)/2)
# == (width**8).bit_length() // 2
#
# (Note that 8*log2(width) can never be an odd integer, so we
# don't care which way half-way cases round in the 'round'
# operation.)
precision = bitwidth - _bit_length(bitwidth ** 8) // 2 + 13

emax = 1 << bitwidth - precision - 1
return Context(
Expand Down
19 changes: 13 additions & 6 deletions bigfloat/test/test_bigfloat.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
EMIN_MIN, EMAX_MAX,

# context constants...
DefaultContext,
half_precision, single_precision,
double_precision, quadruple_precision,
RoundTiesToEven, RoundTowardZero,
RoundTowardPositive, RoundTowardNegative,
RoundAwayFromZero,
ROUND_TIES_TO_EVEN,

# ... and functions
IEEEContext, precision,
Expand All @@ -68,9 +68,6 @@
add, sub, mul, div, fmod, pow,
sqrt, floordiv, mod,

# Version information
MPFR_VERSION_MAJOR, MPFR_VERSION_MINOR,

# 5.5 Basic Arithmetic Functions
root,

Expand Down Expand Up @@ -107,6 +104,16 @@
long_integer_type = int


# Context at the start of each test method.
DefaultTestContext = Context(
precision=53,
rounding=ROUND_TIES_TO_EVEN,
emax=EMAX_MAX,
emin=EMIN_MIN,
subnormalize=False,
)


def diffBigFloat(x, y, match_precisions=True):
"""Determine whether two BigFloat instances can be considered
identical. Returns None on sucess (indicating that the two
Expand Down Expand Up @@ -186,7 +193,7 @@ class PoorObject(object):

class BigFloatTests(unittest.TestCase):
def setUp(self):
setcontext(DefaultContext)
setcontext(DefaultTestContext)

def test_version(self):
self.assertIsInstance(__version__, str)
Expand Down Expand Up @@ -1901,7 +1908,7 @@ def test_divide_by_zero(self):

class ABCTests(unittest.TestCase):
def setUp(self):
setcontext(DefaultContext)
setcontext(DefaultTestContext)


def mpfr_set_str2(rop, s, base, rnd):
Expand Down
102 changes: 89 additions & 13 deletions bigfloat/test/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@
else:
import unittest

import mpfr
from bigfloat.context import (
setcontext,
getcontext,
Context,
DefaultContext,

RoundAwayFromZero,
RoundTiesToEven,
RoundTowardNegative,
RoundTowardPositive,
RoundTowardZero,
_temporary_exponent_bounds,
)
import mpfr
from bigfloat.ieee import quadruple_precision
from bigfloat.rounding_mode import (
ROUND_TIES_TO_EVEN,
ROUND_TOWARD_ZERO,
Expand All @@ -48,9 +52,6 @@


class ContextTests(unittest.TestCase):
def setUp(self):
setcontext(DefaultContext)

def test__temporary_exponent_bounds(self):
# Failed calls to _temporary_exponent_bounds shouldn't affect emin or
# emax.
Expand Down Expand Up @@ -84,7 +85,13 @@ def test__temporary_exponent_bounds(self):
self.assertEqual(mpfr.mpfr_get_emax(), original_emax)

def test_attributes(self):
c = DefaultContext
c = Context(
emin=-999,
emax=999,
precision=100,
subnormalize=True,
rounding=ROUND_TIES_TO_EVEN,
)
self.assertIsInstance(c.precision, int)
self.assertIsInstance(c.emax, int)
self.assertIsInstance(c.emin, int)
Expand All @@ -95,11 +102,80 @@ def test_bad_rounding_mode(self):
with self.assertRaises(ValueError):
Context(rounding=-1)

def test_default_context(self):
self.assertEqual(
DefaultContext,
quadruple_precision + RoundTiesToEven,
)

def test_rounding_contexts(self):
with RoundTiesToEven:
self.assertEqual(getcontext().rounding, ROUND_TIES_TO_EVEN)
with RoundTowardPositive:
self.assertEqual(getcontext().rounding, ROUND_TOWARD_POSITIVE)
with RoundTowardNegative:
self.assertEqual(getcontext().rounding, ROUND_TOWARD_NEGATIVE)
with RoundTiesToEven:
self.assertEqual(getcontext().rounding, ROUND_TIES_TO_EVEN)
with RoundAwayFromZero:
self.assertEqual(getcontext().rounding, ROUND_AWAY_FROM_ZERO)

# Rounding contexts should not affect existing settings for
# precision, exponents, etc.
original_contexts = [
Context(
precision=234,
emin=-9999,
emax=9999,
subnormalize=True,
rounding=ROUND_TOWARD_NEGATIVE,
),
Context(
precision=5,
emin=-10,
emax=10,
subnormalize=False,
rounding=ROUND_AWAY_FROM_ZERO,
),
]
rounding_contexts = [
RoundTiesToEven,
RoundTowardPositive,
RoundTowardNegative,
RoundTowardZero,
RoundAwayFromZero,
]

for original_context in original_contexts:
for rounding_context in rounding_contexts:
with original_context:
with rounding_context:
self.assertEqual(
getcontext().precision,
original_context.precision,
)
self.assertEqual(
getcontext().emin,
original_context.emin,
)
self.assertEqual(
getcontext().emax,
original_context.emax,
)
self.assertEqual(
getcontext().subnormalize,
original_context.subnormalize,
)
self.assertEqual(
getcontext().rounding,
rounding_context.rounding,
)

def test_hashable(self):
# create equal but non-identical contexts
c1 = Context(emin=-999, emax=999, precision=100,
subnormalize=True, rounding=mpfr.MPFR_RNDU)
c2 = (Context(emax=999, emin=-999, rounding=mpfr.MPFR_RNDU) +
subnormalize=True, rounding=ROUND_TOWARD_POSITIVE)
c2 = (Context(emax=999, emin=-999, rounding=ROUND_TOWARD_POSITIVE) +
Context(precision=100, subnormalize=True))
self.assertEqual(hash(c1), hash(c2))
self.assertEqual(c1, c2)
Expand All @@ -108,18 +184,18 @@ def test_hashable(self):

# distinct contexts
d1 = Context(emin=-999, emax=999, precision=100,
subnormalize=True, rounding=mpfr.MPFR_RNDU)
subnormalize=True, rounding=ROUND_TOWARD_POSITIVE)
d2 = Context(emin=-999, emax=999, precision=101,
subnormalize=True, rounding=mpfr.MPFR_RNDU)
subnormalize=True, rounding=ROUND_TOWARD_POSITIVE)
self.assertIs(d1 != d2, True)
self.assertIs(d1 == d2, False)

def test_with(self):
# check use of contexts in with statements
c = Context(emin=-123, emax=456, precision=1729,
subnormalize=True, rounding=mpfr.MPFR_RNDU)
subnormalize=True, rounding=ROUND_TOWARD_POSITIVE)
d = Context(emin=0, emax=10585, precision=20,
subnormalize=False, rounding=mpfr.MPFR_RNDD)
subnormalize=False, rounding=ROUND_TOWARD_NEGATIVE)

with c:
# check nested with
Expand Down
8 changes: 6 additions & 2 deletions bigfloat/test/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@

from bigfloat import (
BigFloat,
DefaultContext,
double_precision,
RoundTiesToEven,
setcontext,
)


DefaultTestContext = double_precision + RoundTiesToEven


class TestFormatting(unittest.TestCase):
def setUp(self):
setcontext(DefaultContext)
setcontext(DefaultTestContext)

def test_format(self):
# Fixed precision formatting.
Expand Down