Numbers#

Recipes for how values are shown: precision, scientific and engineering notation, and floating-point noise. The guide behind them is Formatting numbers.

Set the precision of one variable#

Asked in #182 and #164.

Put a format command at the end of the line. It applies to that line only:

A = 1 / 3              # hc: -f .2f
B = 2 / 3              # hc: -f .4f
C = A + B              # hc: -f .1f
Output (MarkdownRenderer)
\[\begin{aligned} A &= \frac{1}{3} = 0.33 \\ B &= \frac{2}{3} = 0.6667 \\ C &= A + B = 0.3 + 0.7 = 1.0 \end{aligned}\]
$$\begin{aligned} A &= \frac{1}{3} = 0.33 \\ B &= \frac{2}{3} = 0.6667 \\ C &= A + B = 0.3 + 0.7 = 1.0 \end{aligned}$$

Set the precision for part of a calculation#

A command on its own line applies to every line after it, until another command changes it:

a = 10 / 3
# hc: -f .2f
b = a * 2
c = b / 7
# hc: -f .5g
d = c * 1000
Output (MarkdownRenderer)
\[\begin{aligned} a &= \frac{10}{3} = 3.3333 \end{aligned}\]
\[\begin{aligned} b &= a \cdot 2 = 3.33 \cdot 2 = 6.67 \\ c &= \frac{b}{7} = \frac{6.67}{7} = 0.95 \end{aligned}\]
\[\begin{aligned} d &= c \cdot 1000 = 0.95238 \cdot 1000 = 952.38 \end{aligned}\]
$$\begin{aligned} a &= \frac{10}{3} = 3.3333 \end{aligned}$$

$$\begin{aligned} b &= a \cdot 2 = 3.33 \cdot 2 = 6.67 \\ c &= \frac{b}{7} = \frac{6.67}{7} = 0.95 \end{aligned}$$

$$\begin{aligned} d &= c \cdot 1000 = 0.95238 \cdot 1000 = 952.38 \end{aligned}$$

To change the default for a whole session, use handcalcs.set_option("format_code", ".3g") or, in Jupyter, %hc_format_code .3g.

Show large and small numbers in scientific notation#

Asked in #171 and #121.

The default format, .5g, already switches to scientific notation for very large or small values. To always use it, choose an e format:

E = 200000.0           # hc: -f .2e
I = 8.3e7 / 1e12       # hc: -f .3e
EI = E * I             # hc: -f .3e
Output (MarkdownRenderer)
\[\begin{aligned} E &= 2.00 \times 10^{5} \\ I &= \frac{8.3 \times 10^{7}}{10^{12}} = 8.300 \times 10^{-5} \\ \mathrm{EI} &= E \cdot I = 2.000 \times 10^{5} \cdot 8.300 \times 10^{-5} = 1.660 \times 10^{1} \end{aligned}\]
$$\begin{aligned} E &= 2.00 \times 10^{5} \\ I &= \frac{8.3 \times 10^{7}}{10^{12}} = 8.300 \times 10^{-5} \\ \mathrm{EI} &= E \cdot I = 2.000 \times 10^{5} \cdot 8.300 \times 10^{-5} = 1.660 \times 10^{1} \end{aligned}$$

To keep plain decimals instead, use an f format such as # hc: -f .4f.

Show small values instead of zero#

Asked in #230.

Fixed-point formats round small values away (0.0001 with .2f is 0.00). The default .5g keeps five significant figures whatever the magnitude:

k = 0.00012345
k2 = k * 3
Output (MarkdownRenderer)
\[\begin{aligned} k &= 0.00012345 \\ \mathrm{k2} &= k \cdot 3 = 0.00012345 \cdot 3 = 0.00037035 \end{aligned}\]
$$\begin{aligned} k &= 0.00012345 \\ \mathrm{k2} &= k \cdot 3 = 0.00012345 \cdot 3 = 0.00037035 \end{aligned}$$

Drop trailing zeros#

Asked in #221.

The g formats drop trailing zeros; the f formats keep them:

a = 2.5                # hc: -f .3g
b = a * 2              # hc: -f .3g
c = a * 2              # hc: -f .3f
Output (MarkdownRenderer)
\[\begin{aligned} a &= 2.5 \\ b &= a \cdot 2 = 2.5 \cdot 2 = 5 \\ c &= a \cdot 2 = 2.500 \cdot 2 = 5.000 \end{aligned}\]
$$\begin{aligned} a &= 2.5 \\ b &= a \cdot 2 = 2.5 \cdot 2 = 5 \\ c &= a \cdot 2 = 2.500 \cdot 2 = 5.000 \end{aligned}$$

Thousands separators#

P = 1234567.891        # hc: -f ,.1f
Q = P * 2              # hc: -f ,.0f
Output (MarkdownRenderer)
\[\begin{aligned} P &= 1,234,567.9 \\ Q &= P \cdot 2 = 1,234,568 \cdot 2 = 2,469,136 \end{aligned}\]
$$\begin{aligned} P &= 1,234,567.9 \\ Q &= P \cdot 2 = 1,234,568 \cdot 2 = 2,469,136 \end{aligned}$$

Engineering notation (exponents in multiples of three)#

Asked in #202 and #121.

Python’s format codes can’t fix the exponent to a multiple of three, but a renderer can. This subclass replaces the constant handler, which formats every calculated value, and reads two new context fields that you set with comment commands:

  • format_code = "eng4": engineering notation with four significant figures;

  • zero_tol = 1e-9: show anything smaller than the tolerance as 0 (see the next recipe).

"""Engineering notation and a zero tolerance for calculated values (#202, #109)."""
import math

from handcalcs.renderers import LatexRenderer, MarkdownRenderer
from handcalcs.renderers.base import is_source_number
from handcalcs.renderers.latex import format_latex_number


def engineering(value: float, digits: int) -> str:
    """182600000.0 -> '182.6 \\times 10^{6}': the exponent is a multiple of 3."""
    if value == 0 or not math.isfinite(value):
        return format_latex_number(value, "g")
    exponent = 3 * math.floor(math.log10(abs(value)) / 3)
    mantissa = f"{value / 10**exponent:.{digits}g}"
    return mantissa if exponent == 0 else f"{mantissa} \\times 10^{{{exponent}}}"


def number_handler(parent):
    """Wrap a renderer's 'constant' handler with two new context fields."""

    def render_constant(renderer, node, base_context):
        context = base_context.current
        value = node.value
        calculated = isinstance(value, float) and not is_source_number(node)
        if calculated:
            # '# hc: zero_tol = 1e-9' shows anything smaller as 0.
            zero_tol = getattr(context, "zero_tol", None)
            if zero_tol is not None and abs(value) < zero_tol:
                return "0"
            # '# hc: format_code = "eng4"' shows 4 significant digits, exponent a multiple of 3.
            if context.format_code.startswith("eng"):
                return engineering(value, int(context.format_code[3:] or 4))
        return parent(renderer, node, base_context)

    return render_constant


class EngMarkdownRenderer(MarkdownRenderer):
    name = "eng_markdown"


class EngLatexRenderer(LatexRenderer):
    name = "eng_latex"


EngMarkdownRenderer.register("constant")(number_handler(MarkdownRenderer.node_handlers["constant"]))
EngLatexRenderer.register("constant")(number_handler(LatexRenderer.node_handlers["constant"]))
# hc: format_code = "eng4"
P = 1826000.0 * 100
E = 200000.0 / 3
f = 0.000123 * 4
Output (EngMarkdownRenderer)
\[\begin{aligned} P &= 1826000.0 \cdot 100 = 182.6 \times 10^{6} \\ E &= \frac{200000.0}{3} = 66.67 \times 10^{3} \\ f &= 0.000123 \cdot 4 = 492 \times 10^{-6} \end{aligned}\]
$$\begin{aligned} P &= 1826000.0 \cdot 100 = 182.6 \times 10^{6} \\ E &= \frac{200000.0}{3} = 66.67 \times 10^{3} \\ f &= 0.000123 \cdot 4 = 492 \times 10^{-6} \end{aligned}$$

Use EngLatexRenderer for LaTeX output. In Jupyter, both are available by name once the module is imported: %hc_default_renderer eng_markdown.

Snap floating-point noise to zero#

Asked in #109.

0.1 + 0.2 - 0.3 is 5.55e-17 in floating point. The renderer above also reads a zero_tol field:

# hc: zero_tol = 1e-9
x = 0.1 + 0.2 - 0.3
y = 0.1 + 0.2
Output (EngMarkdownRenderer)
\[\begin{aligned} x &= 0.1 + 0.2 - 0.3 = 0 \\ y &= 0.1 + 0.2 = 0.3 \end{aligned}\]
$$\begin{aligned} x &= 0.1 + 0.2 - 0.3 = 0 \\ y &= 0.1 + 0.2 = 0.3 \end{aligned}$$

Only the display changes: x is still 5.55e-17 in Python.

Show text values#

Asked in #157.

Strings are shown in quotes, so a calculation can carry a material grade or a decision:

grade = "S355"
f_y = 355  # MPa
Output (MarkdownRenderer)
\[\begin{aligned} \mathrm{grade} &= \text{"S355"} \\ f_{y} &= 355 \quad\text{(MPa)} \end{aligned}\]
$$\begin{aligned} \mathrm{grade} &= \text{"S355"} \\ f_{y} &= 355 \quad\text{(MPa)} \end{aligned}$$

Next: Showing and hiding steps.