diff options
author | Eevee (Lexy Munroe) <eevee.git@veekun.com> | 2016-06-08 18:43:11 -0700 |
---|---|---|
committer | Eevee (Lexy Munroe) <eevee.git@veekun.com> | 2016-06-08 18:43:11 -0700 |
commit | 9ea91782b4b4dacc144454db14c32e9302e9347b (patch) | |
tree | d5eef8f47153aeba3ce3c077829b946203917ac9 /scss | |
parent | 8ded7bef22819a080299a3b0dd32fd08b8dbdff7 (diff) | |
download | pyscss-9ea91782b4b4dacc144454db14c32e9302e9347b.tar.gz |
Avoid precision loss when converting between units. Fixes #330
Diffstat (limited to 'scss')
-rw-r--r-- | scss/cssdefs.py | 19 | ||||
-rw-r--r-- | scss/tests/files/bugs/unit-float-precision.css | 3 | ||||
-rw-r--r-- | scss/tests/files/bugs/unit-float-precision.scss | 7 | ||||
-rw-r--r-- | scss/types.py | 17 |
4 files changed, 36 insertions, 10 deletions
diff --git a/scss/cssdefs.py b/scss/cssdefs.py index 5d9b7eb..79be7b4 100644 --- a/scss/cssdefs.py +++ b/scss/cssdefs.py @@ -1,6 +1,7 @@ """Constants and functions defined by the CSS specification, not specific to Sass. """ +from fractions import Fraction from math import pi import re @@ -166,15 +167,15 @@ BASE_UNIT_CONVERSIONS = { # Lengths 'mm': (1, 'mm'), 'cm': (10, 'mm'), - 'in': (25.4, 'mm'), - 'px': (25.4 / 96, 'mm'), - 'pt': (25.4 / 72, 'mm'), - 'pc': (25.4 / 6, 'mm'), + 'in': (Fraction(254, 10), 'mm'), + 'px': (Fraction(254, 960), 'mm'), + 'pt': (Fraction(254, 720), 'mm'), + 'pc': (Fraction(254, 60), 'mm'), # Angles - 'deg': (1 / 360, 'turn'), - 'grad': (1 / 400, 'turn'), - 'rad': (pi / 2, 'turn'), + 'deg': (Fraction(1, 360), 'turn'), + 'grad': (Fraction(1, 400), 'turn'), + 'rad': (Fraction.from_float(pi / 2), 'turn'), 'turn': (1, 'turn'), # Times @@ -187,7 +188,7 @@ BASE_UNIT_CONVERSIONS = { # Resolutions 'dpi': (1, 'dpi'), - 'dpcm': (2.54, 'dpi'), + 'dpcm': (Fraction(254 / 100), 'dpi'), 'dppx': (96, 'dpi'), } @@ -252,7 +253,7 @@ def cancel_base_units(units, to_remove): # Copy the dict since we're about to mutate it to_remove = to_remove.copy() remaining_units = [] - total_factor = 1 + total_factor = Fraction(1) for unit in units: factor, base_unit = get_conversion_factor(unit) diff --git a/scss/tests/files/bugs/unit-float-precision.css b/scss/tests/files/bugs/unit-float-precision.css new file mode 100644 index 0000000..16e9d86 --- /dev/null +++ b/scss/tests/files/bugs/unit-float-precision.css @@ -0,0 +1,3 @@ +a { + width: 702px; +} diff --git a/scss/tests/files/bugs/unit-float-precision.scss b/scss/tests/files/bugs/unit-float-precision.scss new file mode 100644 index 0000000..a57b294 --- /dev/null +++ b/scss/tests/files/bugs/unit-float-precision.scss @@ -0,0 +1,7 @@ +// A number of pixels is actually stored internally as millimeters, which +// causes some slight precision issues in corner cases. In this case, the +// answer came out as 701px rather than 702px, because the subtraction yielded +// 701.99999999px. +a { + width: floor(742px - 40px); +} diff --git a/scss/types.py b/scss/types.py index e733377..c35905a 100644 --- a/scss/types.py +++ b/scss/types.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from collections import Iterable import colorsys +from fractions import Fraction import operator import re import string @@ -255,7 +256,21 @@ class Number(Value): self.unit_denom = amount.unit_denom return - if not isinstance(amount, (int, float)): + # Numbers with units are stored internally as a "base" unit, which can + # involve float division, which can lead to precision errors in obscure + # cases. Storing the original units would only partially solve this + # problem, because there'd still be a possible loss of precision when + # converting in Sass-land. Almost all of the conversion factors are + # simple ratios of small whole numbers, so using Fraction across the + # board preserves as much precision as possible. + # TODO in fact, i wouldn't mind parsing Sass values as fractions of a + # power of ten! + # TODO this slowed the test suite down by about 10%, ha + if isinstance(amount, (int, float)): + amount = Fraction.from_float(amount) + elif isinstance(amount, Fraction): + pass + else: raise TypeError("Expected number, got %r" % (amount,)) if unit is not None: |