-
-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Open
Labels
Description
Problem
- It is common in Engineering to print numbers with a given amount of significant figures. That means total amount of figures in a number.
- The Exponent is a multiple of three encoded as a letter suffix (as EngFormatter is already implementing).
- matplotlib.ticker.EngFormatter uses the argument "places" as figures behind the decimal points
- the resulting representation suggests a variable accuracy of the value when using a multiple 3 for the exponent, which is bad
- this also makes the string representation varying in length, which is also bad.
Examples:
- for places=4 expected 12.35 M ; actual: 12.3457 M
- for places=5 expected 12.346 M; actual: 12.34568 M
- for places=4 expected 123.5 µ; actual: 123.4568 µ
- for places=5 expected 12.346 m ; actual: 12.34568 m
Proposed solution
- This overloaded class illustrates the desired formatting :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import math
import numpy as np
from matplotlib.ticker import EngFormatter
class eng(EngFormatter):
def format_eng(self, num):
"""
Format a number in engineering notation, appending a letter
representing the power of 1000 of the original number.
Some examples:
>>> format_eng(0) # for self.places = 0
'0'
>>> format_eng(1000000) # for self.places = 1
'1.0 M'
>>> format_eng(-1e-6) # for self.places = 2
'-1.00 \N{MICRO SIGN}'
"""
sign = 1
if num < 0:
sign = -1
num = -num
if num != 0:
pow10 = int(math.floor(math.log10(num) / 3) * 3)
else:
pow10 = 0
# Force num to zero, to avoid inconsistencies like
# format_eng(-0) = "0" and format_eng(0.0) = "0"
# but format_eng(-0.0) = "-0.0"
num = 0.0
pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES))
mant = sign * num / (10.0 ** pow10)
if self.places is None:
fmt = "g"
else:
if self.places < 3:
raise ValueError("places must be at least 3")
mant_len = len(str(int(mant)))
fmt = f".{self.places - mant_len:d}f"
# Taking care of the cases like 999.9..., which may be rounded to 1000
# instead of 1 k. Beware of the corner case of values that are beyond
# the range of SI prefixes (i.e. > 'Y').
if (abs(float(format(mant, fmt))) >= 1000
and pow10 < max(self.ENG_PREFIXES)):
mant /= 1000
pow10 += 3
fmt = f".{self.places - len(str(int(mant))):d}f"
prefix = self.ENG_PREFIXES[int(pow10)]
if self._usetex or self._useMathText:
formatted = f"${mant:{fmt}}${self.sep}{prefix}"
else:
formatted = f"{mant:{fmt}}{self.sep}{prefix}"
return formatted
def test_eng_formatter():
newFormatter=True
if newFormatter:
print("Using patched eng formatter")
formatter3 = eng(places=3)
formatter4 = eng(places=4)
formatter5 = eng(places=5)
else:
print("Using matplotlib.ticker.EngFormatter")
formatter3 = EngFormatter(places=3)
formatter4 = EngFormatter(places=4)
formatter5 = EngFormatter(places=5)
print(formatter4(12_345_678.90))
print(formatter5(12_345_678.90))
print(formatter4(0.0001234567890))
print(formatter5(0.01234567890))
print(formatter3(999.9))
print(formatter4(999.9))
print(formatter5(999.9))
if newFormatter:
assert formatter4(12_345_678.90) == "12.35 M"
assert formatter5(12_345_678.90) == "12.346 M"
assert formatter4(0.0001234567890) == "123.5 µ"
assert formatter5(0.01234567890) == "12.346 m"
test_eng_formatter()