File: util.py

package info (click to toggle)
python-asv-runner 0.2.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 420 kB
  • sloc: python: 1,631; makefile: 13
file content (164 lines) | stat: -rw-r--r-- 4,448 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
Various low-level utilities.
"""

import math
import shutil

terminal_width = shutil.get_terminal_size().columns


def ceildiv(numerator, denominator):
    """
    Calculate the ceiling division of two numbers.

    #### Parameters
    **numerator** (`int`)
    : The numerator in the division.

    **denominator** (`int`)
    : The denominator in the division.

    #### Returns
    `int`: The result of the division rounded up to the nearest integer.

    #### Notes
    This function calculates the ceiling division of two numbers, i.e.,
    division that rounds up. It is equivalent to `math.ceil(numerator/denominator)`,
    but avoids the conversion of numerator and denominator to float.
    """
    return -((-numerator) // denominator)


def human_float(value, significant=3, truncate_small=None, significant_zeros=False):
    """
    Formats a float into a human-friendly string.

    #### Parameters
    **value** (`float`)
    : The float value to format.

    **significant** (`int`)
    : Number of significant digits to include in the output. Default is 3.

    **truncate_small** (`int`, optional)
    : If defined, leading zeros of numbers < 1 are counted as significant.

    **significant_zeros** (`bool`)
    : If True, trailing unnecessary zeros are included. Default is False.

    #### Returns
    `str`: A string representing the float with human-friendly significant
    digits.

    #### Notes
    Switches to scientific notation for very large or very small numbers.
    The magnitude of the number is calculated using `math.log10(value)`.
    """
    if value == 0:
        return "0"
    elif math.isinf(value) or math.isnan(value):
        return f"{value}"
    elif value < 0:
        sign = "-"
        value = -value
    else:
        sign = ""

    logv = math.log10(value)
    magnitude = int(math.floor(logv)) + 1

    if truncate_small is not None:
        magnitude = max(magnitude, -truncate_small + 1)

    num_digits = significant - magnitude

    if magnitude <= -5 or magnitude >= 9:
        # Too many digits, use scientific notation
        fmt = f"{{0:.{significant}e}}"
    elif value == int(value) or num_digits <= 0:
        value = int(round(value, num_digits))
        fmt = "{0:d}"
    else:
        fmt = f"{{0:.{num_digits}f}}"

    formatted = sign + fmt.format(value)

    if not significant_zeros and "." in formatted and "e" not in fmt:
        formatted = formatted.rstrip("0")
        if formatted[-1] == ".":
            formatted = formatted[:-1]

    if significant_zeros and "." not in formatted:
        if len(formatted) < significant:
            formatted += "." + "0" * (significant - len(formatted))

    return formatted


_human_time_units = (
    ("ns", 0.000000001),
    ("μs", 0.000001),
    ("ms", 0.001),
    ("s", 1),
    ("m", 60),
    ("h", 60 * 60),
    ("d", 60 * 60 * 24),
    ("w", 60 * 60 * 24 * 7),
    ("y", 60 * 60 * 24 * 7 * 52),
    ("C", 60 * 60 * 24 * 7 * 52 * 100),
)


def human_time(seconds, err=None):
    """
    Formats a duration in seconds into a human-friendly time string.

    Depending on the number of seconds given, can be one of::

        1w 3d
        2d 4h
        1h 5m
        1m 4s
          15s

    The representation is always exactly 6 characters long.

    #### Parameters
    **seconds** (`int`)
    : The number of seconds to represent.

    **err** (`float`, optional)
    : If provided, formats the duration as "{value}±{err}", e.g., "1h±5m".
      It can be used to represent the uncertainty in the measurement.

    #### Returns
    `str`: A human-friendly representation of the given duration. If the
           duration is NaN, returns "n/a".
    """
    units = _human_time_units
    seconds = float(seconds)

    scale = seconds

    if scale == 0 and err is not None:
        scale = float(err)

    if scale == 0:
        # Represent zero in reasonable units
        units = [("s", 1), ("m", 60)]

    if scale != scale:
        # nan
        return "n/a"

    for i in range(len(units) - 1):
        if scale < units[i + 1][1]:
            str_time = human_float(seconds / units[i][1], 3, significant_zeros=True)
            if err is None:
                return f"{str_time:s}{units[i][0]}"
            str_err = human_float(err / units[i][1], 1, truncate_small=2)
            return f"{str_time:s}±{str_err:s}{units[i][0]}"
    return "~0"