From 16f9778c6727531b4c06e407648c0c16e1a6ae3c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 6 Jul 2016 12:30:49 -0700 Subject: [PATCH 1/2] Don't convert vmin, vmax to floats. They may be float128's in which case precision would be lost; this can result in `Normalize` returning values (barely) outside of `[0, 1]`. (The cast to `float` was introduced in 28e1d2, referring to bug 2997687 on SF; it may be worth checking what it was about.) --- lib/matplotlib/colors.py | 5 +---- lib/matplotlib/tests/test_colors.py | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 511f46d18710..0ffca3879df5 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -918,8 +918,6 @@ def __call__(self, value, clip=None): elif vmin > vmax: raise ValueError("minvalue must be less than or equal to maxvalue") else: - vmin = float(vmin) - vmax = float(vmax) if clip: mask = np.ma.getmask(result) result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax), @@ -938,8 +936,7 @@ def __call__(self, value, clip=None): def inverse(self, value): if not self.scaled(): raise ValueError("Not invertible until scaled") - vmin = float(self.vmin) - vmax = float(self.vmax) + vmin, vmax = self.vmin, self.vmax if cbook.iterable(value): val = np.ma.asarray(value) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 072959eb8ff7..ee5a32564eee 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -194,6 +194,11 @@ def test_Normalize(): _scalar_tester(norm, vals) _mask_tester(norm, vals) + # Don't lose precision on longdoubles (float128 on Linux). + vals = np.array([1.2345678901, 9.8765432109], dtype=np.longdouble) + norm = mcolors.Normalize(vals.min(), vals.max()) + assert_array_equal(np.asarray(norm(vals)), [0, 1]) + def test_SymLogNorm(): """ From ff78a069bc1ad261ce4001d8347934e3995579b0 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 7 Jul 2016 19:13:51 -0700 Subject: [PATCH 2/2] Proper norm'ing of float128 scalars too. --- lib/matplotlib/colors.py | 23 +++++++---------------- lib/matplotlib/tests/test_colors.py | 7 ++++++- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 0ffca3879df5..73e0c8570794 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -880,22 +880,13 @@ def process_value(value): Experimental; we may want to add an option to force the use of float32. """ - if cbook.iterable(value): - is_scalar = False - result = np.ma.asarray(value) - if result.dtype.kind == 'f': - # this is overkill for lists of floats, but required - # to support pd.Series as input until we can reliable - # determine if result and value share memory in all cases - # (list, tuple, deque, ndarray, Series, ...) - result = result.copy() - elif result.dtype.itemsize > 2: - result = result.astype(float) - else: - result = result.astype(np.float32) - else: - is_scalar = True - result = np.ma.array([value]).astype(float) + is_scalar = not cbook.iterable(value) + if is_scalar: + value = [value] + dtype = np.min_scalar_type(value) + dtype = (np.float32 if dtype.itemsize <= 2 + else np.promote_types(dtype, float)) + result = np.ma.array(value, dtype=dtype, copy=True) return result, is_scalar def __call__(self, value, clip=None): diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index ee5a32564eee..986a5c9ca5cb 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -194,10 +194,15 @@ def test_Normalize(): _scalar_tester(norm, vals) _mask_tester(norm, vals) - # Don't lose precision on longdoubles (float128 on Linux). + # Don't lose precision on longdoubles (float128 on Linux): + # for array inputs... vals = np.array([1.2345678901, 9.8765432109], dtype=np.longdouble) norm = mcolors.Normalize(vals.min(), vals.max()) assert_array_equal(np.asarray(norm(vals)), [0, 1]) + # and for scalar ones. + eps = np.finfo(np.longdouble).resolution + norm = plt.Normalize(1, 1 + 100 * eps) + assert_equal(norm(1 + 50 * eps), .5) def test_SymLogNorm():