{fixed,round}Tools: factor out nearestMultipleShortestRepr
extract the code from fixedToStr into its own function (defined in roundTools), to be used for serializing fractional F2Dot14 angles for COLRv1 PaintRotate etc.
This commit is contained in:
parent
aae11e45c2
commit
f05691246a
@ -17,7 +17,7 @@ functions for converting between fixed-point, float and string representations.
|
||||
The maximum value that can still fit in an F2Dot14. (1.99993896484375)
|
||||
"""
|
||||
|
||||
from .roundTools import otRound
|
||||
from .roundTools import otRound, nearestMultipleShortestRepr
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -125,6 +125,7 @@ def fixedToStr(value, precisionBits):
|
||||
This is pretty slow compared to the simple division used in ``fixedToFloat``.
|
||||
Use sporadically when you need to serialize or print the fixed-point number in
|
||||
a human-readable form.
|
||||
It uses nearestMultipleShortestRepr under the hood.
|
||||
|
||||
Args:
|
||||
value (int): The fixed-point value to convert.
|
||||
@ -133,27 +134,8 @@ def fixedToStr(value, precisionBits):
|
||||
Returns:
|
||||
str: A string representation of the value.
|
||||
"""
|
||||
if not value: return "0.0"
|
||||
|
||||
scale = 1 << precisionBits
|
||||
value /= scale
|
||||
eps = .5 / scale
|
||||
lo = value - eps
|
||||
hi = value + eps
|
||||
# If the range of valid choices spans an integer, return the integer.
|
||||
if int(lo) != int(hi):
|
||||
return str(float(round(value)))
|
||||
fmt = "%.8f"
|
||||
lo = fmt % lo
|
||||
hi = fmt % hi
|
||||
assert len(lo) == len(hi) and lo != hi
|
||||
for i in range(len(lo)):
|
||||
if lo[i] != hi[i]:
|
||||
break
|
||||
period = lo.find('.')
|
||||
assert period < i
|
||||
fmt = "%%.%df" % (i - period)
|
||||
return fmt % value
|
||||
return nearestMultipleShortestRepr(value/scale, factor=1.0/scale)
|
||||
|
||||
|
||||
def strToFixed(string, precisionBits):
|
||||
@ -214,7 +196,7 @@ def floatToFixedToStr(value, precisionBits):
|
||||
This uses the shortest decimal representation (ie. the least
|
||||
number of fractional decimal digits) to represent the equivalent
|
||||
fixed-point number with ``precisionBits`` fractional binary digits.
|
||||
It uses fixedToStr under the hood.
|
||||
It uses nearestMultipleShortestRepr under the hood.
|
||||
|
||||
>>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
|
||||
'-0.61884'
|
||||
@ -227,8 +209,8 @@ def floatToFixedToStr(value, precisionBits):
|
||||
str: A string representation of the value.
|
||||
|
||||
"""
|
||||
fixed = otRound(value * (1 << precisionBits))
|
||||
return fixedToStr(fixed, precisionBits)
|
||||
scale = 1 << precisionBits
|
||||
return nearestMultipleShortestRepr(value, factor=1.0/scale)
|
||||
|
||||
|
||||
def ensureVersionIsLong(value):
|
||||
|
@ -56,3 +56,50 @@ def roundFunc(tolerance, round=otRound):
|
||||
return round
|
||||
|
||||
return functools.partial(maybeRound, tolerance=tolerance, round=round)
|
||||
|
||||
|
||||
def nearestMultipleShortestRepr(value: float, factor: float) -> str:
|
||||
"""Round to nearest multiple of factor and return shortest decimal representation.
|
||||
|
||||
This chooses the float that is closer to a multiple of the given factor while
|
||||
having the shortest decimal representation (the least number of fractional decimal
|
||||
digits).
|
||||
|
||||
For example, given the following:
|
||||
|
||||
>>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14))
|
||||
'-0.61884'
|
||||
|
||||
Useful when you need to serialize or print a fixed-point number (or multiples
|
||||
thereof, such as F2Dot14 fractions of 180 degrees in COLRv1 PaintRotate) in
|
||||
a human-readable form.
|
||||
|
||||
Args:
|
||||
value (value): The value to be rounded and serialized.
|
||||
factor (float): The value which the result is a close multiple of.
|
||||
|
||||
Returns:
|
||||
str: A compact string representation of the value.
|
||||
"""
|
||||
if not value:
|
||||
return "0.0"
|
||||
|
||||
value = otRound(value / factor) * factor
|
||||
eps = .5 * factor
|
||||
lo = value - eps
|
||||
hi = value + eps
|
||||
# If the range of valid choices spans an integer, return the integer.
|
||||
if int(lo) != int(hi):
|
||||
return str(float(round(value)))
|
||||
|
||||
fmt = "%.8f"
|
||||
lo = fmt % lo
|
||||
hi = fmt % hi
|
||||
assert len(lo) == len(hi) and lo != hi
|
||||
for i in range(len(lo)):
|
||||
if lo[i] != hi[i]:
|
||||
break
|
||||
period = lo.find('.')
|
||||
assert period < i
|
||||
fmt = "%%.%df" % (i - period)
|
||||
return fmt % value
|
||||
|
Loading…
x
Reference in New Issue
Block a user