├── .coveragerc
├── requirements.txt
├── benchmark
├── telco-bench.b
├── telco_fractions.py
└── microbench.py
├── MANIFEST.in
├── .gitignore
├── .github
├── dependabot.yml
└── workflows
│ ├── wheels.yml
│ └── ci.yml
├── tox.ini
├── .travis.yml
├── pyproject.toml
├── README.rst
├── Makefile
├── dedup_wheels.py
├── appveyor.yml
├── setup.py
├── appveyor_env.cmd
├── CHANGES.rst
├── src
├── formatfloat_testcases.txt
└── quicktions.pyx
└── LICENSE
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | plugins = Cython.Coverage
3 | source = src
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tox
2 | pytest
3 | coverage
4 | Cython>=3.1.7
5 | codecov
6 |
--------------------------------------------------------------------------------
/benchmark/telco-bench.b:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scoder/quicktions/HEAD/benchmark/telco-bench.b
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include MANIFEST.in LICENSE *.rst
2 | include setup.py *.yml tox.ini *.cmd *.txt .coveragerc
3 | recursive-include src *.py *.pyx *.pxd *.c *.txt *.html
4 | recursive-include benchmark *.py telco-bench.b
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | __pycache__
4 | *.so
5 | *.o
6 |
7 | *.egg
8 | *.egg-info
9 |
10 | /venv
11 | /build
12 | /dist
13 | /wheel*/
14 | src/*.c
15 | src/*.html
16 | MANIFEST
17 |
18 | .tox
19 | .coverage
20 | .idea
21 | callgrind.*
22 | perf*.data
23 | *.patch
24 | *.orig
25 | *.rej
26 | *.dep
27 | *.swp
28 | *~
29 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | # Check for updates to GitHub Actions every week
8 | interval: "weekly"
9 | groups:
10 | github-actions:
11 | patterns:
12 | - "*"
13 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py{38,39,310,311,312,313,314}
3 |
4 | [testenv]
5 | platform =
6 | windows: win32
7 | linux: linux
8 | darwin: darwin
9 | deps =
10 | Cython==3.1.7
11 | pytest
12 | #pytest-cov
13 | #coverage
14 | passenv = *
15 | setenv =
16 | #WITH_COVERAGE=1
17 | commands =
18 | python -m pytest -v src/test_fractions.py --capture=no --strict {posargs}
19 | # coverage run --parallel-mode --source=src -m pytest src/test_fractions.py --capture=no --strict {posargs}
20 | # coverage combine
21 | # coverage report -m --include=src/test_fractions.py #--include=src/quicktions.pyx
22 | # {windows,linux}: codecov
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | #env:
2 | # global:
3 | # - TWINE_USERNAME=scoder
4 | # # Note: TWINE_PASSWORD is set in Travis settings
5 |
6 | os: linux
7 | sudo: false
8 |
9 | language: python
10 |
11 | install:
12 | - pip install -r requirements-appveyor.txt tox-travis
13 | - python setup.py build_ext --inplace --with-cython
14 |
15 | script:
16 | - tox
17 | - python setup.py sdist bdist_wheel
18 | # the following is stolen from https://github.com/joerick/pyinstrument_cext/blob/master/.travis.yml
19 | # uncomment to push wheels automatically to pypi for tagged releases only (requires TWINE_PASSWORD to be set)
20 | # - |
21 | # if [[ $TRAVIS_TAG ]]; then
22 | # python -m pip install twine
23 | # python -m twine upload wheelhouse/*.whl
24 | # fi
25 |
26 | matrix:
27 | include:
28 | - python: 2.7
29 | - python: 3.9
30 | - python: 3.8
31 | - python: 3.7
32 | - python: 3.5
33 | - python: 3.6
34 | #- python: 3.9-dev
35 | # - os: osx
36 | # osx_image: xcode6.4
37 | # env: PY=2
38 | # python: 2
39 | # language: c
40 | # compiler: clang
41 | # cache: false
42 | # - os: osx
43 | # osx_image: xcode6.4
44 | # env: PY=3
45 | # python: 3
46 | # language: c
47 | # compiler: clang
48 | # cache: false
49 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["Cython>=3.1.7", "setuptools"]
3 |
4 | [tool.cibuildwheel]
5 | build-verbosity = 2
6 | skip = ["pp*", "*-musllinux_i686", "cp35*", "cp36*", "cp37*"]
7 | enable =["cpython-freethreading", "cpython-prerelease"]
8 | # test-command = "python -m unittest {package}/test_fractions.py -p -v"
9 |
10 | [tool.cibuildwheel.linux]
11 | archs = ["x86_64", "aarch64", "i686", "ppc64le", "armv7l", "riscv64"]
12 | repair-wheel-command = "auditwheel repair --strip -w {dest_dir} {wheel}"
13 |
14 | [tool.cibuildwheel.linux.environment]
15 | CFLAGS = "-O3 -g1 -pipe -fPIC"
16 | AR = "gcc-ar"
17 | NM = "gcc-nm"
18 | RANLIB = "gcc-ranlib"
19 |
20 | [[tool.cibuildwheel.overrides]]
21 | select = "*linux_i686"
22 | inherit.environment = "append"
23 | environment.CFLAGS = "-O3 -g1 -pipe -fPIC -march=core2 -mtune=generic -mno-ssse3"
24 |
25 | [[tool.cibuildwheel.overrides]]
26 | select = "*linux_x86_64"
27 | inherit.environment = "append"
28 | environment.CFLAGS = "-O3 -g1 -pipe -fPIC -march=core2 -mtune=generic -mno-ssse3"
29 |
30 | [[tool.cibuildwheel.overrides]]
31 | select = "*aarch64"
32 | inherit.environment = "append"
33 | environment.CFLAGS = "-O3 -g1 -pipe -fPIC -march=armv8-a -mtune=cortex-a72"
34 |
35 | [tool.cibuildwheel.windows]
36 | archs = ["AMD64", "x86"]
37 |
38 | [tool.cibuildwheel.macos]
39 | # https://cibuildwheel.readthedocs.io/en/stable/faq/#what-to-provide suggests to provide
40 | # x86_64 and one of universal2 or arm64 wheels. x86_64 is still required by older pips,
41 | # so additional arm64 wheels suffice.
42 | #archs = ["x86_64", "universal2"]
43 | archs = ["x86_64", "arm64"]
44 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==========
2 | quicktions
3 | ==========
4 |
5 | Python's ``Fraction`` data type is an excellent way to do exact calculations
6 | with unlimited rational numbers and largely beats ``Decimal`` in terms of
7 | simplicity, accuracy and safety. Clearly not in terms of speed, though,
8 | given the cdecimal accelerator in Python 3.3+.
9 |
10 | ``quicktions`` is an adaptation of the original ``fractions`` module
11 | (as included in CPython 3.13) that is compiled and optimised with
12 | `Cython `_ into a fast, native extension module.
13 |
14 | Compared to the standard library ``fractions`` module of CPython 3.12,
15 | computations in ``quicktions`` are about 2-4x faster.
16 |
17 | Instantiation of a ``Fraction`` in ``quicktions`` is also
18 |
19 | - 5-15x faster from a floating point string value (e.g. ``Fraction("123.456789")``)
20 | - 3-5x faster from a floating point value (e.g. ``Fraction(123.456789)``)
21 | - 2-5x faster from an integer numerator-denominator pair (e.g. ``Fraction(123, 456)``)
22 |
23 | We provide a set of micro-benchmarks here:
24 |
25 | https://github.com/scoder/quicktions/tree/master/benchmark
26 |
27 | As of quicktions 1.19, the different number types and implementations compare
28 | as follows in CPython 3.12 (measured on Ubuntu Linux):
29 |
30 | .. code-block::
31 |
32 | Average times for all 'create' benchmarks:
33 | float : 19.69 us (1.0x)
34 | Fraction : 58.63 us (3.0x)
35 | Decimal : 84.32 us (4.3x)
36 | PyFraction : 208.20 us (10.6x)
37 |
38 | Average times for all 'compute' benchmarks:
39 | float : 1.79 us (1.0x)
40 | Decimal : 10.11 us (5.7x)
41 | Fraction : 39.24 us (22.0x)
42 | PyFraction : 96.23 us (53.9x)
43 |
44 | While not as fast as the C implemented ``decimal`` module in Python 3,
45 | ``quicktions`` is about 15x faster than the Python implemented ``decimal``
46 | module in Python 2.7.
47 |
48 | For documentation, see the Python standard library's ``fractions`` module:
49 |
50 | https://docs.python.org/3/library/fractions.html
51 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PYTHON?=python3
2 | VERSION?=$(shell sed -ne "s|^__version__\s*=\s*'\([^']*\)'.*|\1|p" src/quicktions.pyx)
3 | PACKAGE=quicktions
4 | WITH_CYTHON := $(shell $(PYTHON) -c 'from Cython.Build import cythonize' 2>/dev/null && echo "--with-cython")
5 | PYTHON_WHEEL_BUILD_VERSION := "cp*"
6 |
7 | MANYLINUX_IMAGES= \
8 | manylinux1_x86_64 \
9 | manylinux1_i686 \
10 | manylinux_2_24_x86_64 \
11 | manylinux_2_24_i686 \
12 | manylinux_2_24_aarch64 \
13 | manylinux_2_28_x86_64 \
14 | manylinux_2_34_x86_64 \
15 | manylinux_2_28_aarch64 \
16 | manylinux_2_34_aarch64 \
17 | musllinux_1_1_x86_64 \
18 | musllinux_1_2_x86_64
19 |
20 | .PHONY: all local sdist test clean realclean wheel_manylinux
21 |
22 | all: local
23 |
24 | local:
25 | ${PYTHON} setup.py build_ext --inplace $(WITH_CYTHON)
26 |
27 | sdist: dist/$(PACKAGE)-$(VERSION).tar.gz
28 |
29 | dist/$(PACKAGE)-$(VERSION).tar.gz:
30 | $(PYTHON) setup.py sdist $(WITH_CYTHON)
31 |
32 | testslow: local
33 | PYTHONPATH=src $(PYTHON) src/test_fractions.py
34 |
35 | test: local
36 | PYTHONPATH=src $(PYTHON) src/test_fractions.py --fast
37 |
38 | clean:
39 | rm -fr build src/*.so
40 |
41 | realclean: clean
42 | rm -fr src/*.c src/*.html
43 |
44 | qemu-user-static:
45 | docker run --rm --privileged hypriot/qemu-register
46 |
47 | wheel:
48 | $(PYTHON) setup.py bdist_wheel
49 |
50 | wheel_manylinux: sdist $(addprefix wheel_,$(MANYLINUX_IMAGES))
51 | $(addprefix wheel_,$(filter-out %_x86_64, $(filter-out %_i686, $(MANYLINUX_IMAGES)))): qemu-user-static
52 |
53 | wheel_%: dist/$(PACKAGE)-$(VERSION).tar.gz
54 | echo "Building wheels for $(PACKAGE) $(VERSION)"
55 | time docker run --rm -t \
56 | -v $(shell pwd):/io \
57 | -e CFLAGS="-O3 -g0 -mtune=generic -pipe -fPIC" \
58 | -e LDFLAGS="$(LDFLAGS) -fPIC" \
59 | -e WHEELHOUSE=wheelhouse$(subst wheel_manylinux,,$@) \
60 | quay.io/pypa/$(subst wheel_,,$@) \
61 | bash -c 'for PYBIN in /opt/python/$(PYTHON_WHEEL_BUILD_VERSION)/bin; do \
62 | $$PYBIN/python -V; \
63 | { $$PYBIN/pip wheel -w /io/$$WHEELHOUSE /io/$< & } ; \
64 | done; wait; \
65 | for whl in /io/$$WHEELHOUSE/$(PACKAGE)-$(VERSION)-*-linux_*.whl; do auditwheel repair $$whl -w /io/$$WHEELHOUSE; done'
66 |
--------------------------------------------------------------------------------
/dedup_wheels.py:
--------------------------------------------------------------------------------
1 | """
2 | Split wheels by set of architectures and delete redundant ones.
3 | """
4 |
5 | import argparse
6 | import os
7 | import pathlib
8 | import re
9 | import sys
10 |
11 | from collections import defaultdict
12 | from typing import Iterable
13 |
14 |
15 | def list_wheels(wheel_dir: pathlib.Path):
16 | return (wheel.name for wheel in wheel_dir.glob("*.whl"))
17 |
18 |
19 | def dedup_wheels(wheel_names: Iterable[str]):
20 | split_wheelname = re.compile(r"(?P\w+-[0-9a-z.]+)-(?P[^-]+-[^-]+)-(?P(?:[^.]+[.])+)whl").match
21 |
22 | keep: set = set()
23 | seen: dict[tuple[str,str],list[set[str]]] = defaultdict(list)
24 | best: dict[tuple[str,str],list[str]] = defaultdict(list)
25 |
26 | for wheel in sorted(wheel_names, key=len, reverse=True):
27 | parts = split_wheelname(wheel)
28 | if not parts:
29 | keep.add(wheel)
30 | continue
31 |
32 | archs = set(parts['archs'].rstrip('.').split('.'))
33 | key = (parts['project'], parts['python'])
34 | for archs_seen in seen[key]:
35 | if not (archs - archs_seen):
36 | break
37 | else:
38 | seen[key].append(archs)
39 | best[key].append(wheel)
40 |
41 | keep.update(wheel for wheel_list in best.values() for wheel in wheel_list)
42 | return sorted(keep)
43 |
44 |
45 | def print_wheels(wheel_names: Iterable[str]):
46 | for wheel in sorted(wheel_names):
47 | print(f" {wheel}")
48 |
49 |
50 | def main(wheel_dir: str, delete=True):
51 | wheel_path = pathlib.Path(wheel_dir)
52 | all_wheels = list(list_wheels(wheel_path))
53 | wheels = dedup_wheels(all_wheels)
54 | redundant = set(all_wheels).difference(wheels)
55 |
56 | if delete:
57 | for wheel in sorted(redundant):
58 | print(f"deleting {wheel}")
59 | os.unlink(wheel_path / wheel)
60 | elif redundant:
61 | print("Redundant wheels found:")
62 | print_wheels(redundant)
63 |
64 |
65 | def parse_args(args):
66 | parser = argparse.ArgumentParser()
67 | parser.add_argument("directory")
68 | parser.add_argument("-d", "--delete", action="store_true")
69 |
70 | return parser.parse_args(args)
71 |
72 |
73 | if __name__ == "__main__":
74 | if len(sys.argv) > 1:
75 | args = parse_args(sys.argv[1:])
76 | main(args.directory, delete=args.delete)
77 | else:
78 | print(f"Usage: {sys.argv[0]} WHEEL_DIR", file=sys.stderr)
79 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2019
2 |
3 | # parts from
4 | # - matrix: https://github.com/pythonnet/pythonnet/blob/master/appveyor.yml
5 | # - Visual Studio 2010 py33 py34 on x64: https://github.com/ogrisel/python-appveyor-demo/blob/master/appveyor/run_with_env.cmd
6 |
7 | branches:
8 | only:
9 | - /default/
10 |
11 | platform:
12 | - x86
13 | - x64
14 |
15 | environment:
16 | global:
17 | # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
18 | # /E:ON and /V:ON options are not enabled in the batch script intepreter
19 | # See: http://stackoverflow.com/a/13751649/163740
20 | CMD_IN_ENV: 'cmd /E:ON /V:ON /C .\appveyor_env.cmd'
21 |
22 | # CIBW_BEFORE_BUILD: python build_ext --with-cython --inplace
23 | # TWINE_USERNAME: scoder
24 | # Note: TWINE_PASSWORD is set in Appveyor settings
25 |
26 | matrix:
27 | - PYTHON_VERSION: 2.7
28 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013
29 | - PYTHON_VERSION: "3.11"
30 | - PYTHON_VERSION: "3.10"
31 | - PYTHON_VERSION: 3.9
32 | - PYTHON_VERSION: 3.8
33 | - PYTHON_VERSION: 3.7
34 | - PYTHON_VERSION: 3.6
35 |
36 | init:
37 | - set PY_VER=%PYTHON_VERSION:.=%
38 | - set TOXENV=py%PY_VER%
39 | - set PYTHON=C:\PYTHON%PY_VER%
40 | - if %PLATFORM%==x64 (set PYTHON=%PYTHON%-x64)
41 | - set TOXPYTHON=%PYTHON%\python.exe
42 |
43 | # Put desired Python version in PATH
44 | - set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
45 |
46 | - 'ECHO %TOXENV% '
47 | - python --version
48 |
49 | install:
50 | #- pip install --upgrade pip
51 | - pip install -r requirements.txt
52 | - python setup.py build_ext --inplace --with-cython
53 |
54 | build: false # First tests then build (is python not C)
55 |
56 | test_script:
57 | - tox -e %TOXENV%-windows
58 |
59 | after_test:
60 | - pip install --upgrade wheel
61 | - python setup.py build_ext --inplace
62 | - rm -rf build # account for: https://bitbucket.org/pypa/wheel/issues/147/bdist_wheel-should-start-by-cleaning-up
63 | - python setup.py sdist bdist_wheel
64 |
65 | artifacts:
66 | # bdist_wheel puts your built wheel in the dist directory
67 | - path: "dist\\*.whl"
68 | name: Wheels
69 | # Where in the appveyor cloud are the wheels pushed to?
70 | # https://www.appveyor.com/docs/packaging-artifacts/#permalink-to-the-last-successful-build-artifact
71 | # borrowed from https://github.com/joerick/pyinstrument_cext/blob/master/appveyor.yml#L11-L15
72 | # - ps: >-
73 | # if ($env:APPVEYOR_REPO_TAG -eq "true") {
74 | # python -m pip install twine
75 | # python -m twine upload (resolve-path wheelhouse\*.whl)
76 | # }
77 |
78 | on_failure:
79 | - ps: dir "env:"
80 | - ps: get-content .tox\*\log\*
81 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import re
4 | import sys
5 |
6 | from setuptools import setup, Extension
7 |
8 |
9 | ext_modules = [
10 | Extension("quicktions", ["src/quicktions.pyx"]),
11 | ]
12 |
13 | try:
14 | sys.argv.remove("--with-profile")
15 | except ValueError:
16 | enable_profiling = False
17 | else:
18 | enable_profiling = True
19 |
20 | enable_coverage = os.environ.get("WITH_COVERAGE") == "1"
21 | force_rebuild = os.environ.get("FORCE_REBUILD") == "1"
22 |
23 | try:
24 | sys.argv.remove("--with-cython")
25 | except ValueError:
26 | pass # legacy option
27 |
28 | cythonize = None
29 | try:
30 | sys.argv.remove("--no-cython")
31 | except ValueError:
32 | try:
33 | from Cython.Build import cythonize
34 | from Cython import __version__ as cython_version
35 | import Cython.Compiler.Options as cython_options
36 | cython_options.annotate = True
37 | except ImportError:
38 | print("Cython not found, building without Cython")
39 | cythonize = None
40 | else:
41 | print("Building with Cython %s" % cython_version)
42 | compiler_directives = {}
43 | if enable_profiling:
44 | compiler_directives['profile'] = True
45 | if enable_coverage:
46 | compiler_directives['linetrace'] = True
47 | ext_modules = cythonize(
48 | ext_modules, compiler_directives=compiler_directives, force=force_rebuild)
49 |
50 | if cythonize is None:
51 | for ext_module in ext_modules:
52 | ext_module.sources[:] = [m.replace('.pyx', '.c') for m in ext_module.sources]
53 | elif enable_coverage:
54 | for ext_module in ext_modules:
55 | ext_module.extra_compile_args += [
56 | "-DCYTHON_TRACE_NOGIL=1",
57 | ]
58 |
59 | if sys.platform == "darwin":
60 | try:
61 | if int(os.environ.get("MACOSX_DEPLOYMENT_TARGET", "0").split(".", 1)[0]) >= 11:
62 | if "-arch" not in os.environ.get("CFLAGS", ""):
63 | os.environ["CFLAGS"] = os.environ.get("CFLAGS", "") + " -arch arm64 -arch x86_64"
64 | os.environ["LDFLAGS"] = os.environ.get("LDFLAGS", "") + " -arch arm64 -arch x86_64"
65 | except ValueError:
66 | pass # probably cannot parse "MACOSX_DEPLOYMENT_TARGET"
67 |
68 |
69 | with open('src/quicktions.pyx') as f:
70 | version = re.search(r"__version__\s*=\s*'([^']+)'", f.read(2048)).group(1)
71 |
72 | with open('README.rst') as f:
73 | long_description = ''.join(f.readlines()[3:]).strip()
74 |
75 | with open('CHANGES.rst') as f:
76 | long_description += '\n\n' + f.read()
77 |
78 |
79 | setup(
80 | name="quicktions",
81 | version=version,
82 | description="Fast fractions data type for rational numbers. "
83 | "Cythonized version of 'fractions.Fraction'.",
84 | long_description=long_description,
85 | author="Stefan Behnel",
86 | author_email="stefan_ml@behnel.de",
87 | url="https://github.com/scoder/quicktions",
88 | #bugtrack_url="https://github.com/scoder/quicktions/issues",
89 | license="PSF-2.0",
90 |
91 | ext_modules=ext_modules,
92 | package_dir={'': 'src'},
93 |
94 | classifiers=[
95 | "Development Status :: 6 - Mature",
96 | "Intended Audience :: Developers",
97 | "Operating System :: OS Independent",
98 | "Programming Language :: Python",
99 | "Programming Language :: Python :: 3",
100 | "Programming Language :: Cython",
101 | "Topic :: Scientific/Engineering :: Mathematics",
102 | "Topic :: Office/Business :: Financial",
103 | ],
104 | )
105 |
--------------------------------------------------------------------------------
/appveyor_env.cmd:
--------------------------------------------------------------------------------
1 | :: To build extensions for 64 bit Python 3, we need to configure environment
2 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of:
3 | :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1)
4 | ::
5 | :: To build extensions for 64 bit Python 2, we need to configure environment
6 | :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of:
7 | :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0)
8 | ::
9 | :: 32 bit builds, and 64-bit builds for 3.5 and beyond, do not require specific
10 | :: environment configurations.
11 | ::
12 | :: Note: this script needs to be run with the /E:ON and /V:ON flags for the
13 | :: cmd interpreter, at least for (SDK v7.0)
14 | ::
15 | :: More details at:
16 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows
17 | :: http://stackoverflow.com/a/13751649/163740
18 | ::
19 | :: Author: Olivier Grisel
20 | :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
21 | ::
22 | :: Notes about batch files for Python people:
23 | ::
24 | :: Quotes in values are literally part of the values:
25 | :: SET FOO="bar"
26 | :: FOO is now five characters long: " b a r "
27 | :: If you don't want quotes, don't include them on the right-hand side.
28 | ::
29 | :: The CALL lines at the end of this file look redundant, but if you move them
30 | :: outside of the IF clauses, they do not run properly in the SET_SDK_64==Y
31 | :: case, I don't know why.
32 | @ECHO OFF
33 |
34 | SET COMMAND_TO_RUN=%*
35 | SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows
36 | SET WIN_WDK=c:\Program Files (x86)\Windows Kits\10\Include\wdf
37 |
38 | :: Extract the major and minor versions, and allow for the minor version to be
39 | :: more than 9. This requires the version number to have two dots in it.
40 | SET MAJOR_PYTHON_VERSION=%PYTHON_VERSION:~0,1%
41 | IF "%PYTHON_VERSION:~3,1%" == "." (
42 | SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,1%
43 | ) ELSE (
44 | SET MINOR_PYTHON_VERSION=%PYTHON_VERSION:~2,2%
45 | )
46 |
47 | :: Based on the Python version, determine what SDK version to use, and whether
48 | :: to set the SDK for 64-bit.
49 | IF %MAJOR_PYTHON_VERSION% == 2 (
50 | SET WINDOWS_SDK_VERSION="v7.0"
51 | SET SET_SDK_64=Y
52 | ) ELSE (
53 | IF %MAJOR_PYTHON_VERSION% == 3 (
54 | SET WINDOWS_SDK_VERSION="v7.1"
55 | IF %MINOR_PYTHON_VERSION% LEQ 4 (
56 | SET SET_SDK_64=Y
57 | ) ELSE (
58 | SET SET_SDK_64=N
59 | IF EXIST "%WIN_WDK%" (
60 | :: See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/
61 | REN "%WIN_WDK%" 0wdf
62 | )
63 | )
64 | ) ELSE (
65 | ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%"
66 | EXIT 1
67 | )
68 | )
69 |
70 | IF %PYTHON_ARCH% == 64 (
71 | IF %SET_SDK_64% == Y (
72 | ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture
73 | SET DISTUTILS_USE_SDK=1
74 | SET MSSdk=1
75 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION%
76 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release
77 | ECHO Executing: %COMMAND_TO_RUN%
78 | call %COMMAND_TO_RUN% || EXIT 1
79 | ) ELSE (
80 | ECHO Using default MSVC build environment for 64 bit architecture
81 | ECHO Executing: %COMMAND_TO_RUN%
82 | call %COMMAND_TO_RUN% || EXIT 1
83 | )
84 | ) ELSE (
85 | ECHO Using default MSVC build environment for 32 bit architecture
86 | ECHO Executing: %COMMAND_TO_RUN%
87 | call %COMMAND_TO_RUN% || EXIT 1
88 | )
89 |
--------------------------------------------------------------------------------
/benchmark/telco_fractions.py:
--------------------------------------------------------------------------------
1 | #-*- coding: UTF-8 -*-
2 |
3 | # The MIT License
4 | #
5 | # Permission is hereby granted, free of charge, to any person
6 | # obtaining a copy of this software and associated documentation
7 | # files (the "Software"), to deal in the Software without
8 | # restriction, including without limitation the rights to use,
9 | # copy, modify, merge, publish, distribute, sublicense, and/or
10 | # sell copies of the Software, and to permit persons to whom the
11 | # Software is furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included
14 | # in all copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | # DEALINGS IN THE SOFTWARE.
23 |
24 |
25 | # Adapted for fractions by Stefan Behnel. Original version from
26 | # https://hg.python.org/benchmarks/file/9a1136898539/performance/bm_telco.py
27 |
28 |
29 | """ Telco Benchmark for measuring the performance of Fraction calculations
30 |
31 | http://www2.hursley.ibm.com/decimal/telco.html
32 | http://www2.hursley.ibm.com/decimal/telcoSpec.html
33 |
34 | A call type indicator, c, is set from the bottom (least significant) bit of the duration (hence c is 0 or 1).
35 | A r, r, is determined from the call type. Those calls with c=0 have a low r: 0.0013; the remainder (‘distance calls’) have a ‘premium’ r: 0.00894. (The rates are, very roughly, in Euros or dollarates per second.)
36 | A price, p, for the call is then calculated (p=r*n).
37 | A basic tax, b, is calculated: b=p*0.0675 (6.75%), and the total basic tax variable is then incremented (sumB=sumB+b).
38 | For distance calls: a distance tax, d, is calculated: d=p*0.0341 (3.41%), and then the total distance tax variable is incremented (sumD=sumD+d).
39 | The total price, t, is calculated (t=p+b, and, if a distance call, t=t+d).
40 | The total prices variable is incremented (sumT=sumT+t).
41 | The total price, t, is converted to a string, s.
42 | """
43 |
44 | import os
45 | from math import fsum
46 | from struct import unpack
47 | from time import time
48 |
49 |
50 | def rel_path(*path):
51 | return os.path.join(os.path.dirname(__file__), *path)
52 |
53 | try:
54 | from quicktions import Fraction
55 | except ImportError:
56 | import sys
57 | sys.path.insert(0, rel_path('..', 'src'))
58 |
59 | from quicktions import Fraction
60 |
61 |
62 | filename = rel_path("telco-bench.b")
63 |
64 |
65 | def run(cls):
66 | rates = list(map(cls, ('0.0013', '0.00894')))
67 | basictax = cls("0.0675")
68 | disttax = cls("0.0341")
69 |
70 | values = []
71 | with open(filename, "rb") as infil:
72 | for _ in range(20000):
73 | datum = infil.read(8)
74 | if datum == '': break
75 | n, = unpack('>Q', datum)
76 | values.append(n)
77 |
78 | start = time()
79 |
80 | sumT = cls() # sum of total prices
81 | sumB = cls() # sum of basic tax
82 | sumD = cls() # sum of 'distance' tax
83 |
84 | for n in values:
85 | calltype = n & 1
86 | r = rates[calltype]
87 |
88 | p = r * n
89 | b = p * basictax
90 | sumB += b
91 | t = p + b
92 |
93 | if calltype:
94 | d = p * disttax
95 | sumD += d
96 | t += d
97 |
98 | sumT += t
99 |
100 | end = time()
101 | return end - start
102 |
103 |
104 | def main(n, cls=Fraction):
105 | for _ in range(5):
106 | run(cls) # warmup
107 | times = [run(cls) for _ in range(n)]
108 | return times
109 |
110 |
111 | def percentile(values, percent):
112 | return values[len(values) * percent // 100]
113 |
114 |
115 | if __name__ == "__main__":
116 | import optparse
117 | parser = optparse.OptionParser(
118 | usage="%prog [options]",
119 | description="Test the performance of the Telco fractions benchmark")
120 | parser.add_option("-n", "--num_runs", action="store", type="int", default=200,
121 | dest="num_runs", help="Number of times to repeat the benchmark.")
122 | parser.add_option("--use-decimal", action="store_true", default=False,
123 | dest="use_decimal", help="Run benchmark with Decimal instead of Fraction.")
124 | parser.add_option("--use-stdlib", action="store_true", default=False,
125 | dest="use_stdlib", help="Run benchmark with fractions.Fraction from stdlib.")
126 | options, args = parser.parse_args()
127 |
128 | num_class = Fraction
129 | if options.use_decimal:
130 | from decimal import Decimal as num_class
131 | elif options.use_stdlib:
132 | from fractions import Fraction as num_class
133 |
134 | results = main(options.num_runs, num_class)
135 | #for result in results:
136 | # print(result)
137 | #print()
138 |
139 | results.sort()
140 | print('%.4f (15%%)' % percentile(results, 15))
141 | print('%.4f (median)' % percentile(results, 50))
142 | print('%.4f (85%%)' % percentile(results, 85))
143 | print('%.4f (mean)' % (fsum(results[1:-1]) / (len(results) - 2)))
144 |
--------------------------------------------------------------------------------
/.github/workflows/wheels.yml:
--------------------------------------------------------------------------------
1 | name: Wheel build
2 |
3 | on:
4 | release:
5 | types: [created]
6 | schedule:
7 | # ┌───────────── minute (0 - 59)
8 | # │ ┌───────────── hour (0 - 23)
9 | # │ │ ┌───────────── day of the month (1 - 31)
10 | # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
11 | # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
12 | # │ │ │ │ │
13 | - cron: "42 3 * * 4"
14 | push:
15 | paths:
16 | - .github/workflows/wheels.yml
17 | - requirements.txt
18 | - pyproject.toml
19 | - MANIFEST.in
20 | - Makefile
21 | - setup.py
22 | pull_request:
23 | types: [opened, synchronize, reopened]
24 | paths:
25 | - .github/workflows/wheels.yml
26 | - requirements.txt
27 | - pyproject.toml
28 | - MANIFEST.in
29 | - Makefile
30 | - setup.py
31 | workflow_dispatch:
32 |
33 | concurrency:
34 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
35 | cancel-in-progress: true
36 |
37 | permissions: {}
38 |
39 | jobs:
40 | sdist:
41 | runs-on: ubuntu-latest
42 |
43 | permissions:
44 | contents: write
45 |
46 | steps:
47 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.1
48 |
49 | - name: Set up Python
50 | uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
51 | with:
52 | python-version: "3.x"
53 |
54 | - name: Install Python dependencies
55 | run: python -m pip install -U pip setuptools wheel && python -m pip install -U -r requirements.txt
56 |
57 | - name: Build sdist
58 | run: make sdist
59 |
60 | - name: Upload sdist
61 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
62 | with:
63 | name: sdist
64 | path: dist/*.tar.gz
65 |
66 | generate-wheels-matrix:
67 | # Create a matrix of all architectures & versions to build.
68 | # This enables the next step to run cibuildwheel in parallel.
69 | # From https://iscinumpy.dev/post/cibuildwheel-2-10-0/#only-210
70 | name: Generate wheels matrix
71 | runs-on: ubuntu-latest
72 | outputs:
73 | include: ${{ steps.set-matrix.outputs.include }}
74 | steps:
75 | - uses: actions/checkout@v6.0.1
76 | - name: Install cibuildwheel
77 | # Nb. keep cibuildwheel version pin consistent with job below
78 | run: pipx install cibuildwheel==3.3.0
79 | - id: set-matrix
80 | run: |
81 | MATRIX=$(
82 | {
83 | cibuildwheel --print-build-identifiers --platform linux \
84 | | jq -nRc '{"only": inputs, "os": "ubuntu-latest"}' \
85 | | sed -e '/aarch64\|armv7l/s|ubuntu-latest|ubuntu-24.04-arm|' \
86 | && cibuildwheel --print-build-identifiers --platform macos \
87 | | jq -nRc '{"only": inputs, "os": "macos-latest"}' \
88 | && cibuildwheel --print-build-identifiers --platform windows \
89 | | jq -nRc '{"only": inputs, "os": "windows-2022"}' \
90 | && cibuildwheel --print-build-identifiers --platform windows --archs ARM64 \
91 | | jq -nRc '{"only": inputs, "os": "windows-11-arm"}'
92 | } | jq -sc
93 | )
94 | echo "include=$MATRIX" >> $GITHUB_OUTPUT
95 |
96 | build_wheels:
97 | name: Build ${{ matrix.only }}
98 | needs: generate-wheels-matrix
99 | runs-on: ${{ matrix.os }}
100 |
101 | strategy:
102 | fail-fast: false
103 | matrix:
104 | include: ${{ fromJson(needs.generate-wheels-matrix.outputs.include) }}
105 |
106 | steps:
107 | - name: Check out the repo
108 | uses: actions/checkout@v6.0.1
109 |
110 | - name: Set up QEMU
111 | if: runner.os == 'Linux'
112 | uses: docker/setup-qemu-action@v3
113 | with:
114 | platforms: all
115 |
116 | - name: Build wheels
117 | uses: pypa/cibuildwheel@v3.3.0
118 | with:
119 | only: ${{ matrix.only }}
120 |
121 | - name: Build faster Linux wheels
122 | # also build wheels with the most recent manylinux images and gcc
123 | if: runner.os == 'Linux' && !contains(matrix.only, 'i686')
124 | uses: pypa/cibuildwheel@v3.3.0
125 | env:
126 | CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_34
127 | CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_34 # manylinux_2_39 ?
128 | CIBW_MANYLINUX_ARMV7L_IMAGE: manylinux_2_35
129 | CIBW_MANYLINUX_PPC64LE_IMAGE: manylinux_2_34
130 | CIBW_MANYLINUX_S390X_IMAGE: manylinux_2_34
131 | CIBW_MANYLINUX_RISCV64_IMAGE: manylinux_2_39
132 | CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux_2_34
133 | CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux_2_34
134 | CIBW_MUSLLINUX_X86_64_IMAGE: musllinux_1_2
135 | CIBW_MUSLLINUX_AARCH64_IMAGE: musllinux_1_2
136 | CIBW_MUSLLINUX_PPC64LE_IMAGE: musllinux_1_2
137 | CIBW_MUSLLINUX_S390X_IMAGE: musllinux_1_2
138 | with:
139 | only: ${{ matrix.only }}
140 |
141 | - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
142 | with:
143 | path: ./wheelhouse/*.whl
144 | name: wheels-${{ matrix.only }}
145 |
146 | merge_wheels:
147 | name: Merge wheel archives
148 | needs: build_wheels
149 | runs-on: ubuntu-latest
150 |
151 | steps:
152 | - name: Merge wheels
153 | uses: actions/upload-artifact/merge@v6
154 | with:
155 | name: all_wheels
156 | pattern: wheels-*
157 | delete-merged: true
158 | compression-level: 9
159 |
160 | upload_release_assets:
161 | name: Upload packages
162 | needs: [ sdist, merge_wheels ]
163 | runs-on: ubuntu-latest
164 | if: github.ref_type == 'tag'
165 |
166 | permissions:
167 | contents: write
168 |
169 | steps:
170 | - name: Check out the repo
171 | uses: actions/checkout@v6.0.1
172 |
173 | - name: Download files
174 | uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
175 | with:
176 | path: ./dist_downloads
177 | merge-multiple: true
178 |
179 | - name: List downloaded artifacts
180 | run: ls -la ./dist_downloads
181 |
182 | - name: Deduplicate wheels
183 | run: python3 dedup_wheels.py -d ./dist_downloads
184 |
185 | - name: Release
186 | uses: softprops/action-gh-release@v2
187 | with:
188 | files: ./dist_downloads/*
189 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI tests
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**'
7 | - '!.github/**'
8 | - '.github/workflows/ci.yml'
9 | pull_request:
10 | paths:
11 | - '**'
12 | - '!.github/**'
13 | - '.github/workflows/ci.yml'
14 |
15 | concurrency:
16 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
17 | cancel-in-progress: true
18 |
19 | permissions:
20 | contents: read # to fetch code (actions/checkout)
21 |
22 |
23 | jobs:
24 | sdist:
25 | runs-on: ubuntu-latest
26 |
27 | steps:
28 | - uses: actions/checkout@v6.0.1
29 |
30 | - name: Set up Python
31 | uses: actions/setup-python@v6.1.0
32 | with:
33 | python-version: "3.12"
34 |
35 | - name: Install Dependency
36 | run: |
37 | python -m pip install -U pip setuptools && python -m pip install -r requirements.txt
38 |
39 | - name: Build sdist
40 | run: make sdist
41 |
42 | - name: Archive sdist
43 | uses: actions/upload-artifact@v6
44 | with:
45 | name: sdist
46 | path: dist/*.tar.gz
47 | if-no-files-found: ignore
48 |
49 | Tests:
50 | strategy:
51 | # Allows for matrix sub-jobs to fail without canceling the rest
52 | fail-fast: false
53 |
54 | matrix:
55 | python-version:
56 | - "3.8"
57 | - "3.9"
58 | - "3.10"
59 | - "3.11"
60 | - "3.12"
61 | - "3.13"
62 | - "3.13t"
63 | - "3.14"
64 | - "3.14t"
65 | - "3.15-dev"
66 | - "3.15t-dev"
67 | os: ["ubuntu-latest"]
68 |
69 | #include:
70 | #- python-version: "3.7"
71 | # os: ubuntu-22.04
72 | #- python-version: "3.14-dev"
73 | # allowed_failure: true
74 |
75 | runs-on: ${{ matrix.os }}
76 |
77 | steps:
78 | - uses: actions/checkout@v6.0.1
79 |
80 | - name: Set up Python
81 | uses: actions/setup-python@v6.1.0
82 | with:
83 | python-version: ${{ matrix.python-version }}
84 |
85 | - name: Install Dependency
86 | run: |
87 | python -m pip install -U pip setuptools && python -m pip install -r requirements.txt
88 |
89 | - name: Build
90 | continue-on-error: ${{ matrix.allowed_failure || false }}
91 | run: make local
92 |
93 | - name: Run slow tests
94 | continue-on-error: ${{ matrix.allowed_failure || false }}
95 | run: make testslow
96 |
97 | - name: Running benchmark
98 | if: startsWith(matrix.python-version, '3.')
99 | run: |
100 | # Run different benchmarks.
101 | date; echo "Running telco benchmark ..."
102 | python benchmark/telco_fractions.py -n 250
103 | date; echo "Running micro benchmarks ..."
104 | python benchmark/microbench.py create pidigits
105 | date; echo "Done."
106 |
107 | Linux:
108 |
109 | strategy:
110 | # Allows for matrix sub-jobs to fail without canceling the rest
111 | fail-fast: false
112 |
113 | matrix:
114 | image:
115 | - manylinux_2_24_i686
116 | - manylinux_2_24_x86_64
117 | - manylinux_2_34_x86_64
118 | - musllinux_1_1_x86_64
119 | - musllinux_1_2_x86_64
120 | pyversion: ["cp"]
121 |
122 | include:
123 | - image: manylinux_2_24_aarch64
124 | pyversion: "cp38"
125 | - image: manylinux_2_24_aarch64
126 | pyversion: "cp39"
127 | - image: manylinux_2_24_aarch64
128 | pyversion: "cp310"
129 | - image: manylinux_2_24_aarch64
130 | pyversion: "cp311"
131 | - image: manylinux_2_34_aarch64
132 | pyversion: "cp312"
133 | - image: manylinux_2_34_aarch64
134 | pyversion: "cp313"
135 | - image: manylinux_2_34_aarch64
136 | pyversion: "cp314"
137 |
138 | runs-on: ubuntu-latest
139 |
140 | steps:
141 | - uses: actions/checkout@v6.0.1
142 |
143 | - name: Set up Python
144 | uses: actions/setup-python@v6.1.0
145 | with:
146 | python-version: "3.12"
147 |
148 | - name: Install Dependency
149 | run: |
150 | python -m pip install -U pip setuptools && python -m pip install --pre -r requirements.txt
151 |
152 | - name: Building wheel
153 | run: |
154 | make PYTHON_WHEEL_BUILD_VERSION="${{ matrix.pyversion }}*" sdist wheel_${{ matrix.image }}
155 |
156 | - name: Copy wheels in dist
157 | run: cp -v wheelhouse*/*-m*linux*.whl dist/ # manylinux / musllinux
158 |
159 | - name: Archive Wheels
160 | uses: actions/upload-artifact@v6
161 | with:
162 | name: wheels-${{ matrix.image }}-${{ matrix.pyversion }}
163 | path: dist/*.whl
164 | if-no-files-found: ignore
165 |
166 | non-Linux:
167 | strategy:
168 | # Allows for matrix sub-jobs to fail without canceling the rest
169 | fail-fast: false
170 |
171 | matrix:
172 | os: [macos-latest, windows-latest]
173 | python-version:
174 | - "3.8"
175 | - "3.9"
176 | - "3.10"
177 | - "3.11"
178 | - "3.12"
179 | - "3.13"
180 | - "3.13t"
181 | - "3.14"
182 | - "3.14t"
183 | - "3.15-dev"
184 | - "3.15t-dev"
185 |
186 | #include:
187 | # - python-version: "3.15-dev"
188 | # allowed_failure: true
189 |
190 | runs-on: ${{ matrix.os }}
191 | env: { MACOSX_DEPLOYMENT_TARGET: 11.0 }
192 |
193 | steps:
194 | - uses: actions/checkout@v6.0.1
195 |
196 | - name: Set up Python
197 | uses: actions/setup-python@v6.1.0
198 | with:
199 | python-version: ${{ matrix.python-version }}
200 |
201 | - name: Install dependencies
202 | run: |
203 | python -m pip install -U pip setuptools wheel
204 | python -m pip install --pre -r requirements.txt
205 |
206 | - name: Build wheels
207 | run: make sdist wheel
208 |
209 | - name: Run slow tests
210 | run: make testslow
211 |
212 | - name: Upload wheels
213 | uses: actions/upload-artifact@v6
214 | with:
215 | name: wheels-${{ matrix.os }}-${{ matrix.python-version }}
216 | path: dist/*.whl
217 | if-no-files-found: ignore
218 |
219 | - name: Running benchmark
220 | run: |
221 | # Run different benchmarks.
222 | date; echo "Running telco benchmark ..."
223 | python benchmark/telco_fractions.py -n 250
224 | date; echo "Running micro benchmarks ..."
225 | python benchmark/microbench.py create pidigits
226 | date; echo "Done."
227 |
228 | merge-wheels:
229 | needs: [ Linux, non-Linux ]
230 | runs-on: ubuntu-latest
231 |
232 | steps:
233 | - name: Merge wheels
234 | uses: actions/upload-artifact/merge@v6
235 | with:
236 | name: all_wheels
237 | pattern: wheels-*
238 | delete-merged: true
239 | compression-level: 9
240 |
--------------------------------------------------------------------------------
/CHANGES.rst:
--------------------------------------------------------------------------------
1 | ChangeLog
2 | =========
3 |
4 | 1.23 (2025-??-??)
5 | -----------------
6 |
7 | * Thousands separator in formatting was handled differently than for floats etc.
8 | https://github.com/python/cpython/pull/131067
9 |
10 | * Support thousands separators for formatting fractional part of Fraction.
11 | https://github.com/python/cpython/pull/132204
12 |
13 |
14 | 1.22 (2025-08-25)
15 | -----------------
16 |
17 | * A choice of different GCD implementations is available via ``quicktions.use_gcd_impl()``.
18 | The fastest one on the current machine is chosen at import time.
19 |
20 | * Built using Cython 3.1.3.
21 |
22 |
23 | 1.21 (2025-06-13)
24 | -----------------
25 |
26 | * A serious parser bug could accidentally concatenate numerator and denominator
27 | as final denominator when parsing "x/y" where x or y are close to ``sys.maxsize``,
28 | thus returning a ``Fraction("x/xy")``.
29 |
30 | * MSVC and clang now also benefit from fast "count trailing zeroes" intrinsics.
31 |
32 |
33 | 1.20 (2025-06-13)
34 | -----------------
35 |
36 | * ``quicktions`` is compatible with freethreading Python (3.13+).
37 |
38 | * Accept leading zeros in precision/width for Fraction's formatting, following
39 | https://github.com/python/cpython/pull/130663
40 |
41 | * In line with Python's ``Fraction``, quicktions now raises a ``ValueError``
42 | (instead of an ``OverflowError``) when exceeding parser limits, following
43 | https://github.com/python/cpython/pull/134010
44 |
45 | * Call ``__rpow__`` in ternary ``pow()`` if necessary, following
46 | https://github.com/python/cpython/pull/130251
47 |
48 | * Built using Cython 3.1.2.
49 |
50 |
51 | 1.19 (2024-11-29)
52 | -----------------
53 |
54 | * Support for Python 2.7 as well as 3.7 and earlier has been removed.
55 |
56 | * Generally use ``.as_integer_ratio()`` in the constructor if available.
57 | https://github.com/python/cpython/pull/120271
58 |
59 | * Add a classmethod ``.from_number()`` that requires a number argument, not a string.
60 | https://github.com/python/cpython/pull/121800
61 |
62 | * Mixed calculations with other ``Rational`` classes could return the wrong type.
63 | https://github.com/python/cpython/issues/119189
64 |
65 | * In mixed calculations with ``complex``, the Fraction is now converted to ``float``
66 | instead of ``complex`` to avoid certain corner cases in complex calculation.
67 | https://github.com/python/cpython/pull/119839
68 |
69 | * Using ``complex`` numbers in division shows better tracebacks.
70 | https://github.com/python/cpython/pull/102842
71 |
72 | * Subclass instantiations and calculations could fail in some cases.
73 |
74 |
75 | 1.18 (2024-04-03)
76 | -----------------
77 |
78 | * New binary wheels were added built with gcc 12 (manylinux_2_28).
79 |
80 | * x86_64 wheels now require SSE4.2.
81 |
82 | * Built using Cython 3.0.10.
83 |
84 |
85 | 1.17 (2024-03-24)
86 | -----------------
87 |
88 | * Math operations were sped up by inlined binary GCD calculation.
89 |
90 |
91 | 1.16 (2024-01-10)
92 | -----------------
93 |
94 | * Formatting support was improved, following CPython 3.13a3 as of
95 | https://github.com/python/cpython/pull/111320
96 |
97 | * Add support for Python 3.13 by using Cython 3.0.8 and calling ``math.gcd()``.
98 |
99 |
100 | 1.15 (2023-08-27)
101 | -----------------
102 |
103 | * Add support for Python 3.12 by using Cython 3.0.2.
104 |
105 |
106 | 1.14 (2023-03-19)
107 | -----------------
108 |
109 | * Implement ``__format__`` for ``Fraction``, following
110 | https://github.com/python/cpython/pull/100161
111 |
112 | * Implement ``Fraction.is_integer()``, following
113 | https://github.com/python/cpython/issues/100488
114 |
115 | * ``Fraction.limit_denominator()`` is faster, following
116 | https://github.com/python/cpython/pull/93730
117 |
118 | * Internal creation of result Fractions is about 10% faster if the calculated
119 | numerator/denominator pair is already normalised, following
120 | https://github.com/python/cpython/pull/101780
121 |
122 | * Built using Cython 3.0.0b1.
123 |
124 |
125 | 1.13 (2022-01-11)
126 | -----------------
127 |
128 | * Parsing very long numbers from a fraction string was very slow, even slower
129 | than ``fractions.Fraction``. The parser is now faster in all cases (and
130 | still much faster for shorter numbers).
131 |
132 | * ``Fraction`` did not implement ``__int__``.
133 | https://bugs.python.org/issue44547
134 |
135 |
136 | 1.12 (2022-01-07)
137 | -----------------
138 |
139 | * Faster and more space friendly pickling and unpickling.
140 | https://bugs.python.org/issue44154
141 |
142 | * Algorithmically faster arithmetic for large denominators, although slower for
143 | small fraction components.
144 | https://bugs.python.org/issue43420
145 | Original patch for CPython by Sergey B. Kirpichev and Raymond Hettinger.
146 |
147 | * Make sure ``bool(Fraction)`` always returns a ``bool``.
148 | https://bugs.python.org/issue39274
149 |
150 | * Built using Cython 3.0.0a10.
151 |
152 |
153 | 1.11 (2019-12-19)
154 | -----------------
155 |
156 | * Fix ``OverflowError`` when parsing string values with long decimal parts.
157 |
158 |
159 | 1.10 (2019-08-23)
160 | -----------------
161 |
162 | * ``hash(fraction)`` is substantially faster in Py3.8+, following an optimisation
163 | in CPython 3.9 (https://bugs.python.org/issue37863).
164 |
165 | * New method ``fraction.as_integer_ratio()``.
166 |
167 |
168 | 1.9 (2018-12-26)
169 | ----------------
170 |
171 | * Substantially faster normalisation (and therefore instantiation) in Py3.5+.
172 |
173 | * ``//`` (floordiv) now follows the expected rounding behaviour when used with
174 | floats (by converting to float first), and is much faster for integer operations.
175 |
176 | * Fix return type of divmod(), where the first item should be an integer.
177 |
178 | * Further speed up mod and divmod operations.
179 |
180 |
181 | 1.8 (2018-12-26)
182 | ----------------
183 |
184 | * Faster mod and divmod calculation.
185 |
186 |
187 | 1.7 (2018-10-16)
188 | ----------------
189 |
190 | * Faster normalisation and fraction string parsing.
191 |
192 | * Add support for Python 3.7.
193 |
194 | * Built using Cython 0.29.
195 |
196 |
197 | 1.6 (2018-03-23)
198 | ----------------
199 |
200 | * Speed up Fraction creation from a string value by 3-5x.
201 |
202 | * Built using Cython 0.28.1.
203 |
204 |
205 | 1.5 (2017-10-22)
206 | ----------------
207 |
208 | * Result of power operator (``**``) was not normalised for negative values.
209 |
210 | * Built using Cython 0.27.2.
211 |
212 |
213 | 1.4 (2017-09-16)
214 | ----------------
215 |
216 | * Rebuilt using Cython 0.26.1 to improve support of Python 3.7.
217 |
218 |
219 | 1.3 (2016-07-24)
220 | ----------------
221 |
222 | * repair the faster instantiation from Decimal values in Python 3.6
223 |
224 | * avoid potential glitch for certain large numbers in normalisation under Python 2.x
225 |
226 |
227 | 1.2 (2016-04-08)
228 | ----------------
229 |
230 | * change hash function in Python 2.x to match that of ``fractions.Fraction``
231 |
232 |
233 | 1.1 (2016-03-29)
234 | ----------------
235 |
236 | * faster instantiation from float values
237 |
238 | * faster instantiation from Decimal values in Python 3.6
239 |
240 |
241 | 1.0 (2015-09-10)
242 | ----------------
243 |
244 | * ``Fraction.imag`` property could return non-zero
245 |
246 | * parsing strings with long fraction parts could use an incorrect scale
247 |
248 |
249 | 0.7 (2014-10-09)
250 | ----------------
251 |
252 | * faster instantiation from float and string values
253 |
254 | * fix test in Python 2.x
255 |
256 |
257 | 0.6 (2014-10-09)
258 | ----------------
259 |
260 | * faster normalisation (and thus instantiation)
261 |
262 |
263 | 0.5 (2014-10-06)
264 | ----------------
265 |
266 | * faster math operations
267 |
268 |
269 | 0.4 (2014-10-06)
270 | ----------------
271 |
272 | * enable legacy division support in Python 2.x
273 |
274 |
275 | 0.3 (2014-10-05)
276 | ----------------
277 |
278 | * minor behavioural fixes in corner cases under Python 2.x
279 | (now passes all test in Py2.7 as well)
280 |
281 |
282 | 0.2 (2014-10-03)
283 | ----------------
284 |
285 | * cache hash value of Fractions
286 |
287 |
288 | 0.1 (2014-09-24)
289 | ----------------
290 |
291 | * initial public release
292 |
--------------------------------------------------------------------------------
/src/formatfloat_testcases.txt:
--------------------------------------------------------------------------------
1 | -- 'f' code formatting, with explicit precision (>= 0). Output always
2 | -- has the given number of places after the point; zeros are added if
3 | -- necessary to make this true.
4 |
5 | -- zeros
6 | %.0f 0 -> 0
7 | %.1f 0 -> 0.0
8 | %.2f 0 -> 0.00
9 | %.3f 0 -> 0.000
10 | %.50f 0 -> 0.00000000000000000000000000000000000000000000000000
11 |
12 | -- precision 0; result should never include a .
13 | %.0f 1.5 -> 2
14 | %.0f 2.5 -> 2
15 | %.0f 3.5 -> 4
16 | %.0f 0.0 -> 0
17 | %.0f 0.1 -> 0
18 | %.0f 0.001 -> 0
19 | %.0f 10.0 -> 10
20 | %.0f 10.1 -> 10
21 | %.0f 10.01 -> 10
22 | %.0f 123.456 -> 123
23 | %.0f 1234.56 -> 1235
24 | %.0f 1e49 -> 9999999999999999464902769475481793196872414789632
25 | %.0f 9.9999999999999987e+49 -> 99999999999999986860582406952576489172979654066176
26 | %.0f 1e50 -> 100000000000000007629769841091887003294964970946560
27 |
28 | -- precision 1
29 | %.1f 0.0001 -> 0.0
30 | %.1f 0.001 -> 0.0
31 | %.1f 0.01 -> 0.0
32 | %.1f 0.04 -> 0.0
33 | %.1f 0.06 -> 0.1
34 | %.1f 0.25 -> 0.2
35 | %.1f 0.75 -> 0.8
36 | %.1f 1.4 -> 1.4
37 | %.1f 1.5 -> 1.5
38 | %.1f 10.0 -> 10.0
39 | %.1f 1000.03 -> 1000.0
40 | %.1f 1234.5678 -> 1234.6
41 | %.1f 1234.7499 -> 1234.7
42 | %.1f 1234.75 -> 1234.8
43 |
44 | -- precision 2
45 | %.2f 0.0001 -> 0.00
46 | %.2f 0.001 -> 0.00
47 | %.2f 0.004999 -> 0.00
48 | %.2f 0.005001 -> 0.01
49 | %.2f 0.01 -> 0.01
50 | %.2f 0.125 -> 0.12
51 | %.2f 0.375 -> 0.38
52 | %.2f 1234500 -> 1234500.00
53 | %.2f 1234560 -> 1234560.00
54 | %.2f 1234567 -> 1234567.00
55 | %.2f 1234567.8 -> 1234567.80
56 | %.2f 1234567.89 -> 1234567.89
57 | %.2f 1234567.891 -> 1234567.89
58 | %.2f 1234567.8912 -> 1234567.89
59 |
60 | -- alternate form always includes a decimal point. This only
61 | -- makes a difference when the precision is 0.
62 | %#.0f 0 -> 0.
63 | %#.1f 0 -> 0.0
64 | %#.0f 1.5 -> 2.
65 | %#.0f 2.5 -> 2.
66 | %#.0f 10.1 -> 10.
67 | %#.0f 1234.56 -> 1235.
68 | %#.1f 1.4 -> 1.4
69 | %#.2f 0.375 -> 0.38
70 |
71 | -- if precision is omitted it defaults to 6
72 | %f 0 -> 0.000000
73 | %f 1230000 -> 1230000.000000
74 | %f 1234567 -> 1234567.000000
75 | %f 123.4567 -> 123.456700
76 | %f 1.23456789 -> 1.234568
77 | %f 0.00012 -> 0.000120
78 | %f 0.000123 -> 0.000123
79 | %f 0.00012345 -> 0.000123
80 | %f 0.000001 -> 0.000001
81 | %f 0.0000005001 -> 0.000001
82 | %f 0.0000004999 -> 0.000000
83 |
84 | -- 'e' code formatting with explicit precision (>= 0). Output should
85 | -- always have exactly the number of places after the point that were
86 | -- requested.
87 |
88 | -- zeros
89 | %.0e 0 -> 0e+00
90 | %.1e 0 -> 0.0e+00
91 | %.2e 0 -> 0.00e+00
92 | %.10e 0 -> 0.0000000000e+00
93 | %.50e 0 -> 0.00000000000000000000000000000000000000000000000000e+00
94 |
95 | -- precision 0. no decimal point in the output
96 | %.0e 0.01 -> 1e-02
97 | %.0e 0.1 -> 1e-01
98 | %.0e 1 -> 1e+00
99 | %.0e 10 -> 1e+01
100 | %.0e 100 -> 1e+02
101 | %.0e 0.012 -> 1e-02
102 | %.0e 0.12 -> 1e-01
103 | %.0e 1.2 -> 1e+00
104 | %.0e 12 -> 1e+01
105 | %.0e 120 -> 1e+02
106 | %.0e 123.456 -> 1e+02
107 | %.0e 0.000123456 -> 1e-04
108 | %.0e 123456000 -> 1e+08
109 | %.0e 0.5 -> 5e-01
110 | %.0e 1.4 -> 1e+00
111 | %.0e 1.5 -> 2e+00
112 | %.0e 1.6 -> 2e+00
113 | %.0e 2.4999999 -> 2e+00
114 | %.0e 2.5 -> 2e+00
115 | %.0e 2.5000001 -> 3e+00
116 | %.0e 3.499999999999 -> 3e+00
117 | %.0e 3.5 -> 4e+00
118 | %.0e 4.5 -> 4e+00
119 | %.0e 5.5 -> 6e+00
120 | %.0e 6.5 -> 6e+00
121 | %.0e 7.5 -> 8e+00
122 | %.0e 8.5 -> 8e+00
123 | %.0e 9.4999 -> 9e+00
124 | %.0e 9.5 -> 1e+01
125 | %.0e 10.5 -> 1e+01
126 | %.0e 14.999 -> 1e+01
127 | %.0e 15 -> 2e+01
128 |
129 | -- precision 1
130 | %.1e 0.0001 -> 1.0e-04
131 | %.1e 0.001 -> 1.0e-03
132 | %.1e 0.01 -> 1.0e-02
133 | %.1e 0.1 -> 1.0e-01
134 | %.1e 1 -> 1.0e+00
135 | %.1e 10 -> 1.0e+01
136 | %.1e 100 -> 1.0e+02
137 | %.1e 120 -> 1.2e+02
138 | %.1e 123 -> 1.2e+02
139 | %.1e 123.4 -> 1.2e+02
140 |
141 | -- precision 2
142 | %.2e 0.00013 -> 1.30e-04
143 | %.2e 0.000135 -> 1.35e-04
144 | %.2e 0.0001357 -> 1.36e-04
145 | %.2e 0.0001 -> 1.00e-04
146 | %.2e 0.001 -> 1.00e-03
147 | %.2e 0.01 -> 1.00e-02
148 | %.2e 0.1 -> 1.00e-01
149 | %.2e 1 -> 1.00e+00
150 | %.2e 10 -> 1.00e+01
151 | %.2e 100 -> 1.00e+02
152 | %.2e 1000 -> 1.00e+03
153 | %.2e 1500 -> 1.50e+03
154 | %.2e 1590 -> 1.59e+03
155 | %.2e 1598 -> 1.60e+03
156 | %.2e 1598.7 -> 1.60e+03
157 | %.2e 1598.76 -> 1.60e+03
158 | %.2e 9999 -> 1.00e+04
159 |
160 | -- omitted precision defaults to 6
161 | %e 0 -> 0.000000e+00
162 | %e 165 -> 1.650000e+02
163 | %e 1234567 -> 1.234567e+06
164 | %e 12345678 -> 1.234568e+07
165 | %e 1.1 -> 1.100000e+00
166 |
167 | -- alternate form always contains a decimal point. This only makes
168 | -- a difference when precision is 0.
169 |
170 | %#.0e 0.01 -> 1.e-02
171 | %#.0e 0.1 -> 1.e-01
172 | %#.0e 1 -> 1.e+00
173 | %#.0e 10 -> 1.e+01
174 | %#.0e 100 -> 1.e+02
175 | %#.0e 0.012 -> 1.e-02
176 | %#.0e 0.12 -> 1.e-01
177 | %#.0e 1.2 -> 1.e+00
178 | %#.0e 12 -> 1.e+01
179 | %#.0e 120 -> 1.e+02
180 | %#.0e 123.456 -> 1.e+02
181 | %#.0e 0.000123456 -> 1.e-04
182 | %#.0e 123456000 -> 1.e+08
183 | %#.0e 0.5 -> 5.e-01
184 | %#.0e 1.4 -> 1.e+00
185 | %#.0e 1.5 -> 2.e+00
186 | %#.0e 1.6 -> 2.e+00
187 | %#.0e 2.4999999 -> 2.e+00
188 | %#.0e 2.5 -> 2.e+00
189 | %#.0e 2.5000001 -> 3.e+00
190 | %#.0e 3.499999999999 -> 3.e+00
191 | %#.0e 3.5 -> 4.e+00
192 | %#.0e 4.5 -> 4.e+00
193 | %#.0e 5.5 -> 6.e+00
194 | %#.0e 6.5 -> 6.e+00
195 | %#.0e 7.5 -> 8.e+00
196 | %#.0e 8.5 -> 8.e+00
197 | %#.0e 9.4999 -> 9.e+00
198 | %#.0e 9.5 -> 1.e+01
199 | %#.0e 10.5 -> 1.e+01
200 | %#.0e 14.999 -> 1.e+01
201 | %#.0e 15 -> 2.e+01
202 | %#.1e 123.4 -> 1.2e+02
203 | %#.2e 0.0001357 -> 1.36e-04
204 |
205 | -- 'g' code formatting.
206 |
207 | -- zeros
208 | %.0g 0 -> 0
209 | %.1g 0 -> 0
210 | %.2g 0 -> 0
211 | %.3g 0 -> 0
212 | %.4g 0 -> 0
213 | %.10g 0 -> 0
214 | %.50g 0 -> 0
215 | %.100g 0 -> 0
216 |
217 | -- precision 0 doesn't make a lot of sense for the 'g' code (what does
218 | -- it mean to have no significant digits?); in practice, it's interpreted
219 | -- as identical to precision 1
220 | %.0g 1000 -> 1e+03
221 | %.0g 100 -> 1e+02
222 | %.0g 10 -> 1e+01
223 | %.0g 1 -> 1
224 | %.0g 0.1 -> 0.1
225 | %.0g 0.01 -> 0.01
226 | %.0g 1e-3 -> 0.001
227 | %.0g 1e-4 -> 0.0001
228 | %.0g 1e-5 -> 1e-05
229 | %.0g 1e-6 -> 1e-06
230 | %.0g 12 -> 1e+01
231 | %.0g 120 -> 1e+02
232 | %.0g 1.2 -> 1
233 | %.0g 0.12 -> 0.1
234 | %.0g 0.012 -> 0.01
235 | %.0g 0.0012 -> 0.001
236 | %.0g 0.00012 -> 0.0001
237 | %.0g 0.000012 -> 1e-05
238 | %.0g 0.0000012 -> 1e-06
239 |
240 | -- precision 1 identical to precision 0
241 | %.1g 1000 -> 1e+03
242 | %.1g 100 -> 1e+02
243 | %.1g 10 -> 1e+01
244 | %.1g 1 -> 1
245 | %.1g 0.1 -> 0.1
246 | %.1g 0.01 -> 0.01
247 | %.1g 1e-3 -> 0.001
248 | %.1g 1e-4 -> 0.0001
249 | %.1g 1e-5 -> 1e-05
250 | %.1g 1e-6 -> 1e-06
251 | %.1g 12 -> 1e+01
252 | %.1g 120 -> 1e+02
253 | %.1g 1.2 -> 1
254 | %.1g 0.12 -> 0.1
255 | %.1g 0.012 -> 0.01
256 | %.1g 0.0012 -> 0.001
257 | %.1g 0.00012 -> 0.0001
258 | %.1g 0.000012 -> 1e-05
259 | %.1g 0.0000012 -> 1e-06
260 |
261 | -- precision 2
262 | %.2g 1000 -> 1e+03
263 | %.2g 100 -> 1e+02
264 | %.2g 10 -> 10
265 | %.2g 1 -> 1
266 | %.2g 0.1 -> 0.1
267 | %.2g 0.01 -> 0.01
268 | %.2g 0.001 -> 0.001
269 | %.2g 1e-4 -> 0.0001
270 | %.2g 1e-5 -> 1e-05
271 | %.2g 1e-6 -> 1e-06
272 | %.2g 1234 -> 1.2e+03
273 | %.2g 123 -> 1.2e+02
274 | %.2g 12.3 -> 12
275 | %.2g 1.23 -> 1.2
276 | %.2g 0.123 -> 0.12
277 | %.2g 0.0123 -> 0.012
278 | %.2g 0.00123 -> 0.0012
279 | %.2g 0.000123 -> 0.00012
280 | %.2g 0.0000123 -> 1.2e-05
281 |
282 | -- bad cases from http://bugs.python.org/issue9980
283 | %.12g 38210.0 -> 38210
284 | %.12g 37210.0 -> 37210
285 | %.12g 36210.0 -> 36210
286 |
287 | -- alternate g formatting: always include decimal point and
288 | -- exactly significant digits.
289 | %#.0g 0 -> 0.
290 | %#.1g 0 -> 0.
291 | %#.2g 0 -> 0.0
292 | %#.3g 0 -> 0.00
293 | %#.4g 0 -> 0.000
294 |
295 | %#.0g 0.2 -> 0.2
296 | %#.1g 0.2 -> 0.2
297 | %#.2g 0.2 -> 0.20
298 | %#.3g 0.2 -> 0.200
299 | %#.4g 0.2 -> 0.2000
300 | %#.10g 0.2 -> 0.2000000000
301 |
302 | %#.0g 2 -> 2.
303 | %#.1g 2 -> 2.
304 | %#.2g 2 -> 2.0
305 | %#.3g 2 -> 2.00
306 | %#.4g 2 -> 2.000
307 |
308 | %#.0g 20 -> 2.e+01
309 | %#.1g 20 -> 2.e+01
310 | %#.2g 20 -> 20.
311 | %#.3g 20 -> 20.0
312 | %#.4g 20 -> 20.00
313 |
314 | %#.0g 234.56 -> 2.e+02
315 | %#.1g 234.56 -> 2.e+02
316 | %#.2g 234.56 -> 2.3e+02
317 | %#.3g 234.56 -> 235.
318 | %#.4g 234.56 -> 234.6
319 | %#.5g 234.56 -> 234.56
320 | %#.6g 234.56 -> 234.560
321 |
322 | -- repr formatting. Result always includes decimal point and at
323 | -- least one digit after the point, or an exponent.
324 | %r 0 -> 0.0
325 | %r 1 -> 1.0
326 |
327 | %r 0.01 -> 0.01
328 | %r 0.02 -> 0.02
329 | %r 0.03 -> 0.03
330 | %r 0.04 -> 0.04
331 | %r 0.05 -> 0.05
332 |
333 | -- values >= 1e16 get an exponent
334 | %r 10 -> 10.0
335 | %r 100 -> 100.0
336 | %r 1e15 -> 1000000000000000.0
337 | %r 9.999e15 -> 9999000000000000.0
338 | %r 9999999999999998 -> 9999999999999998.0
339 | %r 9999999999999999 -> 1e+16
340 | %r 1e16 -> 1e+16
341 | %r 1e17 -> 1e+17
342 |
343 | -- as do values < 1e-4
344 | %r 1e-3 -> 0.001
345 | %r 1.001e-4 -> 0.0001001
346 | %r 1.0000000000000001e-4 -> 0.0001
347 | %r 1.000000000000001e-4 -> 0.0001000000000000001
348 | %r 1.00000000001e-4 -> 0.000100000000001
349 | %r 1.0000000001e-4 -> 0.00010000000001
350 | %r 1e-4 -> 0.0001
351 | %r 0.99999999999999999e-4 -> 0.0001
352 | %r 0.9999999999999999e-4 -> 9.999999999999999e-05
353 | %r 0.999999999999e-4 -> 9.99999999999e-05
354 | %r 0.999e-4 -> 9.99e-05
355 | %r 1e-5 -> 1e-05
356 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | A. HISTORY OF THE SOFTWARE
2 | ==========================
3 |
4 | Python was created in the early 1990s by Guido van Rossum at Stichting
5 | Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
6 | as a successor of a language called ABC. Guido remains Python's
7 | principal author, although it includes many contributions from others.
8 |
9 | In 1995, Guido continued his work on Python at the Corporation for
10 | National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
11 | in Reston, Virginia where he released several versions of the
12 | software.
13 |
14 | In May 2000, Guido and the Python core development team moved to
15 | BeOpen.com to form the BeOpen PythonLabs team. In October of the same
16 | year, the PythonLabs team moved to Digital Creations (now Zope
17 | Corporation, see http://www.zope.com). In 2001, the Python Software
18 | Foundation (PSF, see http://www.python.org/psf/) was formed, a
19 | non-profit organization created specifically to own Python-related
20 | Intellectual Property. Zope Corporation is a sponsoring member of
21 | the PSF.
22 |
23 | All Python releases are Open Source (see http://www.opensource.org for
24 | the Open Source Definition). Historically, most, but not all, Python
25 | releases have also been GPL-compatible; the table below summarizes
26 | the various releases.
27 |
28 | Release Derived Year Owner GPL-
29 | from compatible? (1)
30 |
31 | 0.9.0 thru 1.2 1991-1995 CWI yes
32 | 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
33 | 1.6 1.5.2 2000 CNRI no
34 | 2.0 1.6 2000 BeOpen.com no
35 | 1.6.1 1.6 2001 CNRI yes (2)
36 | 2.1 2.0+1.6.1 2001 PSF no
37 | 2.0.1 2.0+1.6.1 2001 PSF yes
38 | 2.1.1 2.1+2.0.1 2001 PSF yes
39 | 2.1.2 2.1.1 2002 PSF yes
40 | 2.1.3 2.1.2 2002 PSF yes
41 | 2.2 and above 2.1.1 2001-now PSF yes
42 |
43 | Footnotes:
44 |
45 | (1) GPL-compatible doesn't mean that we're distributing Python under
46 | the GPL. All Python licenses, unlike the GPL, let you distribute
47 | a modified version without making your changes open source. The
48 | GPL-compatible licenses make it possible to combine Python with
49 | other software that is released under the GPL; the others don't.
50 |
51 | (2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
52 | because its license has a choice of law clause. According to
53 | CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
54 | is "not incompatible" with the GPL.
55 |
56 | Thanks to the many outside volunteers who have worked under Guido's
57 | direction to make these releases possible.
58 |
59 |
60 | B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
61 | ===============================================================
62 |
63 | PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
64 | --------------------------------------------
65 |
66 | 1. This LICENSE AGREEMENT is between the Python Software Foundation
67 | ("PSF"), and the Individual or Organization ("Licensee") accessing and
68 | otherwise using this software ("Python") in source or binary form and
69 | its associated documentation.
70 |
71 | 2. Subject to the terms and conditions of this License Agreement, PSF hereby
72 | grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
73 | analyze, test, perform and/or display publicly, prepare derivative works,
74 | distribute, and otherwise use Python alone or in any derivative version,
75 | provided, however, that PSF's License Agreement and PSF's notice of copyright,
76 | i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
77 | 2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved" are
78 | retained in Python alone or in any derivative version prepared by Licensee.
79 |
80 | 3. In the event Licensee prepares a derivative work that is based on
81 | or incorporates Python or any part thereof, and wants to make
82 | the derivative work available to others as provided herein, then
83 | Licensee hereby agrees to include in any such work a brief summary of
84 | the changes made to Python.
85 |
86 | 4. PSF is making Python available to Licensee on an "AS IS"
87 | basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
88 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
89 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
90 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
91 | INFRINGE ANY THIRD PARTY RIGHTS.
92 |
93 | 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
94 | FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
95 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
96 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
97 |
98 | 6. This License Agreement will automatically terminate upon a material
99 | breach of its terms and conditions.
100 |
101 | 7. Nothing in this License Agreement shall be deemed to create any
102 | relationship of agency, partnership, or joint venture between PSF and
103 | Licensee. This License Agreement does not grant permission to use PSF
104 | trademarks or trade name in a trademark sense to endorse or promote
105 | products or services of Licensee, or any third party.
106 |
107 | 8. By copying, installing or otherwise using Python, Licensee
108 | agrees to be bound by the terms and conditions of this License
109 | Agreement.
110 |
111 |
112 | BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
113 | -------------------------------------------
114 |
115 | BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
116 |
117 | 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
118 | office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
119 | Individual or Organization ("Licensee") accessing and otherwise using
120 | this software in source or binary form and its associated
121 | documentation ("the Software").
122 |
123 | 2. Subject to the terms and conditions of this BeOpen Python License
124 | Agreement, BeOpen hereby grants Licensee a non-exclusive,
125 | royalty-free, world-wide license to reproduce, analyze, test, perform
126 | and/or display publicly, prepare derivative works, distribute, and
127 | otherwise use the Software alone or in any derivative version,
128 | provided, however, that the BeOpen Python License is retained in the
129 | Software, alone or in any derivative version prepared by Licensee.
130 |
131 | 3. BeOpen is making the Software available to Licensee on an "AS IS"
132 | basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
133 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
134 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
135 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
136 | INFRINGE ANY THIRD PARTY RIGHTS.
137 |
138 | 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
139 | SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
140 | AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
141 | DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
142 |
143 | 5. This License Agreement will automatically terminate upon a material
144 | breach of its terms and conditions.
145 |
146 | 6. This License Agreement shall be governed by and interpreted in all
147 | respects by the law of the State of California, excluding conflict of
148 | law provisions. Nothing in this License Agreement shall be deemed to
149 | create any relationship of agency, partnership, or joint venture
150 | between BeOpen and Licensee. This License Agreement does not grant
151 | permission to use BeOpen trademarks or trade names in a trademark
152 | sense to endorse or promote products or services of Licensee, or any
153 | third party. As an exception, the "BeOpen Python" logos available at
154 | http://www.pythonlabs.com/logos.html may be used according to the
155 | permissions granted on that web page.
156 |
157 | 7. By copying, installing or otherwise using the software, Licensee
158 | agrees to be bound by the terms and conditions of this License
159 | Agreement.
160 |
161 |
162 | CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
163 | ---------------------------------------
164 |
165 | 1. This LICENSE AGREEMENT is between the Corporation for National
166 | Research Initiatives, having an office at 1895 Preston White Drive,
167 | Reston, VA 20191 ("CNRI"), and the Individual or Organization
168 | ("Licensee") accessing and otherwise using Python 1.6.1 software in
169 | source or binary form and its associated documentation.
170 |
171 | 2. Subject to the terms and conditions of this License Agreement, CNRI
172 | hereby grants Licensee a nonexclusive, royalty-free, world-wide
173 | license to reproduce, analyze, test, perform and/or display publicly,
174 | prepare derivative works, distribute, and otherwise use Python 1.6.1
175 | alone or in any derivative version, provided, however, that CNRI's
176 | License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
177 | 1995-2001 Corporation for National Research Initiatives; All Rights
178 | Reserved" are retained in Python 1.6.1 alone or in any derivative
179 | version prepared by Licensee. Alternately, in lieu of CNRI's License
180 | Agreement, Licensee may substitute the following text (omitting the
181 | quotes): "Python 1.6.1 is made available subject to the terms and
182 | conditions in CNRI's License Agreement. This Agreement together with
183 | Python 1.6.1 may be located on the Internet using the following
184 | unique, persistent identifier (known as a handle): 1895.22/1013. This
185 | Agreement may also be obtained from a proxy server on the Internet
186 | using the following URL: http://hdl.handle.net/1895.22/1013".
187 |
188 | 3. In the event Licensee prepares a derivative work that is based on
189 | or incorporates Python 1.6.1 or any part thereof, and wants to make
190 | the derivative work available to others as provided herein, then
191 | Licensee hereby agrees to include in any such work a brief summary of
192 | the changes made to Python 1.6.1.
193 |
194 | 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
195 | basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
196 | IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
197 | DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
198 | FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
199 | INFRINGE ANY THIRD PARTY RIGHTS.
200 |
201 | 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
202 | 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
203 | A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
204 | OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
205 |
206 | 6. This License Agreement will automatically terminate upon a material
207 | breach of its terms and conditions.
208 |
209 | 7. This License Agreement shall be governed by the federal
210 | intellectual property law of the United States, including without
211 | limitation the federal copyright law, and, to the extent such
212 | U.S. federal law does not apply, by the law of the Commonwealth of
213 | Virginia, excluding Virginia's conflict of law provisions.
214 | Notwithstanding the foregoing, with regard to derivative works based
215 | on Python 1.6.1 that incorporate non-separable material that was
216 | previously distributed under the GNU General Public License (GPL), the
217 | law of the Commonwealth of Virginia shall govern this License
218 | Agreement only as to issues arising under or with respect to
219 | Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
220 | License Agreement shall be deemed to create any relationship of
221 | agency, partnership, or joint venture between CNRI and Licensee. This
222 | License Agreement does not grant permission to use CNRI trademarks or
223 | trade name in a trademark sense to endorse or promote products or
224 | services of Licensee, or any third party.
225 |
226 | 8. By clicking on the "ACCEPT" button where indicated, or by copying,
227 | installing or otherwise using Python 1.6.1, Licensee agrees to be
228 | bound by the terms and conditions of this License Agreement.
229 |
230 | ACCEPT
231 |
232 |
233 | CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
234 | --------------------------------------------------
235 |
236 | Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
237 | The Netherlands. All rights reserved.
238 |
239 | Permission to use, copy, modify, and distribute this software and its
240 | documentation for any purpose and without fee is hereby granted,
241 | provided that the above copyright notice appear in all copies and that
242 | both that copyright notice and this permission notice appear in
243 | supporting documentation, and that the name of Stichting Mathematisch
244 | Centrum or CWI not be used in advertising or publicity pertaining to
245 | distribution of the software without specific, written prior
246 | permission.
247 |
248 | STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
249 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
250 | FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
251 | FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
252 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
253 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
254 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
255 |
--------------------------------------------------------------------------------
/benchmark/microbench.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import itertools
3 | import operator
4 | import os
5 | import statistics
6 | import timeit
7 | from collections import defaultdict
8 |
9 | from decimal import Decimal
10 | from fractions import Fraction as PyFraction
11 |
12 | def rel_path(*path):
13 | return os.path.join(os.path.dirname(__file__), *path)
14 |
15 | try:
16 | from quicktions import Fraction as QFraction
17 | except ImportError:
18 | import sys
19 | sys.path.insert(0, rel_path('..', 'src'))
20 | from quicktions import Fraction as QFraction
21 |
22 | PyFraction.__name__ = "PyFraction"
23 |
24 | class_names, classes = list(zip(*[
25 | ('float', float),
26 | ('Decimal', Decimal),
27 | ('QFraction', QFraction),
28 | ('PyFraction', PyFraction),
29 | ]))
30 |
31 | benchmark_values = [
32 | "123",
33 | "76138945735803",
34 | "123.456789",
35 | "1234e10",
36 | "1234e-10",
37 | "983675298736458927343e10",
38 | "983675298736458927343e-10",
39 | "983675298736458927345e10",
40 | "983675298736458927345e-10",
41 | "983675298736458927343e50",
42 | "983675298736458927343e-50",
43 | "983675298736458927345e60",
44 | "983675298736458927345e-60",
45 | ]
46 |
47 |
48 | benchmark_expressions = [
49 | "a + b + c",
50 | "a - b - c",
51 | "a * b * c",
52 | "a / b / c",
53 | "a * 10 + b * 99 + c",
54 | "a * 10 * b + c",
55 | "a / 10 / b - c",
56 | "a * 32543575324 * b / c",
57 | "(a * 32543575324) / (b * 65767564) * c",
58 | "a ** 3 + b ** 2 + c ** 1",
59 | ]
60 |
61 | PI_DIGITS = (
62 | "3.141592653589793238462643383279502884197169399375105820974944592307816406286"
63 | "208998628034825342117067982148086513282306647093844609550582231725359408128481"
64 | "117450284102701938521105559644622948954930381964428810975665933446128475648233"
65 | "786783165271201909145648566923460348610454326648213393607260249141273724587006"
66 | "606315588174881520920962829254091715364367892590360011330530548820466521384146"
67 | "951941511609433057270365759591953092186117381932611793105118548074462379962749"
68 | "567351885752724891227938183011949129833673362440656643086021394946395224737190"
69 | "702179860943702770539217176293176752384674818467669405132000568127145263560827"
70 | "785771342757789609173637178721468440901224953430146549585371050792279689258923"
71 | "542019956112129021960864034418159813629774771309960518707211349999998372978049"
72 | "951059731732816096318595024459455346908302642522308253344685035261931188171010"
73 | "003137838752886587533208381420617177669147303598253490428755468731159562863882"
74 | "353787593751957781857780532171226806613001927876611195909216420198938095257201"
75 | "065485863278865936153381827968230301952035301852968995773622599413891249721775"
76 | "283479131515574857242454150695950829533116861727855889075098381754637464939319"
77 | "255060400927701671139009848824012858361603563707660104710181942955596198946767"
78 | "837449448255379774726847104047534646208046684259069491293313677028989152104752"
79 | "162056966024058038150193511253382430035587640247496473263914199272604269922796"
80 | "782354781636009341721641219924586315030286182974555706749838505494588586926995"
81 | "690927210797509302955321165344987202755960236480665499119881834797753566369807"
82 | "426542527862551818417574672890977772793800081647060016145249192173217214772350"
83 | "141441973568548161361157352552133475741849468438523323907394143334547762416862"
84 | "518983569485562099219222184272550254256887671790494601653466804988627232791786"
85 | "085784383827967976681454100953883786360950680064225125205117392984896084128488"
86 | "626945604241965285022210661186306744278622039194945047123713786960956364371917"
87 | "287467764657573962413890865832645995813390478027590099465764078951269468398352"
88 | "595709825822620522489407726719478268482601476990902640136394437455305068203496"
89 | "252451749399651431429809190659250937221696461515709858387410597885959772975498"
90 | "930161753928468138268683868942774155991855925245953959431049972524680845987273"
91 | "644695848653836736222626099124608051243884390451244136549762780797715691435997"
92 | "700129616089441694868555848406353422072225828488648158456028506016842739452267"
93 | "467678895252138522549954666727823986456596116354886230577456498035593634568174"
94 | "324112515076069479451096596094025228879710893145669136867228748940560101503308"
95 | "617928680920874760917824938589009714909675985261365549781893129784821682998948"
96 | "722658804857564014270477555132379641451523746234364542858444795265867821051141"
97 | "354735739523113427166102135969536231442952484937187110145765403590279934403742"
98 | "007310578539062198387447808478489683321445713868751943506430218453191048481005"
99 | "370614680674919278191197939952061419663428754440643745123718192179998391015919"
100 | "561814675142691239748940907186494231961567945208095146550225231603881930142093"
101 | "762137855956638937787083039069792077346722182562599661501421503068038447734549"
102 | "202605414665925201497442850732518666002132434088190710486331734649651453905796"
103 | "268561005508106658796998163574736384052571459102897064140110971206280439039759"
104 | "515677157700420337869936007230558763176359421873125147120532928191826186125867"
105 | "321579198414848829164470609575270695722091756711672291098169091528017350671274"
106 | "858322287183520935396572512108357915136988209144421006751033467110314126711136"
107 | "990865851639831501970165151168517143765761835155650884909989859982387345528331"
108 | "635507647918535893226185489632132933089857064204675259070915481416549859461637"
109 | "180270981994309924488957571282890592323326097299712084433573265489382391193259"
110 | "746366730583604142813883032038249037589852437441702913276561809377344403070746"
111 | "921120191302033038019762110110044929321516084244485963766983895228684783123552"
112 | "658213144957685726243344189303968642624341077322697802807318915441101044682325"
113 | "271620105265227211166039666557309254711055785376346682065310989652691862056476"
114 | "931257058635662018558100729360659876486117910453348850346113657686753249441668"
115 | "039626579787718556084552965412665408530614344431858676975145661406800700237877"
116 | "659134401712749470420562230538994561314071127000407854733269939081454664645880"
117 | "797270826683063432858785698305235808933065757406795457163775254202114955761581"
118 | "400250126228594130216471550979259230990796547376125517656751357517829666454779"
119 | "174501129961489030463994713296210734043751895735961458901938971311179042978285"
120 | "647503203198691514028708085990480109412147221317947647772622414254854540332157"
121 | "185306142288137585043063321751829798662237172159160771669254748738986654949450"
122 | "114654062843366393790039769265672146385306736096571209180763832716641627488880"
123 | "078692560290228472104031721186082041900042296617119637792133757511495950156604"
124 | "963186294726547364252308177036751590673502350728354056704038674351362222477158"
125 | "915049530984448933309634087807693259939780541934144737744184263129860809988868"
126 | "741326047215695162396586457302163159819319516735381297416772947867242292465436"
127 | "680098067692823828068996400482435403701416314965897940924323789690706977942236"
128 | "250822168895738379862300159377647165122893578601588161755782973523344604281512"
129 | "627203734314653197777416031990665541876397929334419521541341899485444734567383"
130 | "162499341913181480927777103863877343177207545654532207770921201905166096280490"
131 | "926360197598828161332316663652861932668633606273567630354477628035045077723554"
132 | "710585954870279081435624014517180624643626794561275318134078330336254232783944"
133 | "975382437205835311477119926063813346776879695970309833913077109870408591337464"
134 | "144282277263465947047458784778720192771528073176790770715721344473060570073349"
135 | "243693113835049316312840425121925651798069411352801314701304781643788518529092"
136 | "854520116583934196562134914341595625865865570552690496520985803385072242648293"
137 | "972858478316305777756068887644624824685792603953527734803048029005876075825104"
138 | "747091643961362676044925627420420832085661190625454337213153595845068772460290"
139 | "161876679524061634252257719542916299193064553779914037340432875262888963995879"
140 | "475729174642635745525407909145135711136941091193932519107602082520261879853188"
141 | "770584297259167781314969900901921169717372784768472686084900337702424291651300"
142 | "500516832336435038951702989392233451722013812806965011784408745196012122859937"
143 | "162313017114448464090389064495444006198690754851602632750529834918740786680881"
144 | "833851022833450850486082503930213321971551843063545500766828294930413776552793"
145 | "975175461395398468339363830474611996653858153842056853386218672523340283087112"
146 | "328278921250771262946322956398989893582116745627010218356462201349671518819097"
147 | "303811980049734072396103685406643193950979019069963955245300545058068550195673"
148 | "022921913933918568034490398205955100226353536192041994745538593810234395544959"
149 | "778377902374216172711172364343543947822181852862408514006660443325888569867054"
150 | "315470696574745855033232334210730154594051655379068662733379958511562578432298"
151 | "827372319898757141595781119635833005940873068121602876496286744604774649159950"
152 | "549737425626901049037781986835938146574126804925648798556145372347867330390468"
153 | "838343634655379498641927056387293174872332083760112302991136793862708943879936"
154 | "201629515413371424892830722012690147546684765357616477379467520049075715552781"
155 | "965362132392640616013635815590742202020318727760527721900556148425551879253034"
156 | "351398442532234157623361064250639049750086562710953591946589751413103482276930"
157 | "624743536325691607815478181152843667957061108615331504452127473924544945423682"
158 | "886061340841486377670096120715124914043027253860764823634143346235189757664521"
159 | "641376796903149501910857598442391986291642193994907236234646844117394032659184"
160 | "044378051333894525742399508296591228508555821572503107125701266830240292952522"
161 | "011872676756220415420516184163484756516999811614101002996078386909291603028840"
162 | "026910414079288621507842451670908700069928212066041837180653556725253256753286"
163 | "129104248776182582976515795984703562226293486003415872298053498965022629174878"
164 | "820273420922224533985626476691490556284250391275771028402799806636582548892648"
165 | "802545661017296702664076559042909945681506526530537182941270336931378517860904"
166 | "070866711496558343434769338578171138645587367812301458768712660348913909562009"
167 | "939361031029161615288138437909904231747336394804575931493140529763475748119356"
168 | "709110137751721008031559024853090669203767192203322909433467685142214477379393"
169 | "751703443661991040337511173547191855046449026365512816228824462575916333039107"
170 | "225383742182140883508657391771509682887478265699599574490661758344137522397096"
171 | "834080053559849175417381883999446974867626551658276584835884531427756879002909"
172 | "517028352971634456212964043523117600665101241200659755851276178583829204197484"
173 | "423608007193045761893234922927965019875187212726750798125547095890455635792122"
174 | "103334669749923563025494780249011419521238281530911407907386025152274299581807"
175 | "247162591668545133312394804947079119153267343028244186041426363954800044800267"
176 | "049624820179289647669758318327131425170296923488962766844032326092752496035799"
177 | "646925650493681836090032380929345958897069536534940603402166544375589004563288"
178 | "225054525564056448246515187547119621844396582533754388569094113031509526179378"
179 | "002974120766514793942590298969594699556576121865619673378623625612521632086286"
180 | "922210327488921865436480229678070576561514463204692790682120738837781423356282"
181 | "360896320806822246801224826117718589638140918390367367222088832151375560037279"
182 | "839400415297002878307667094447456013455641725437090697939612257142989467154357"
183 | "846878861444581231459357198492252847160504922124247014121478057345510500801908"
184 | "699603302763478708108175450119307141223390866393833952942578690507643100638351"
185 | "983438934159613185434754649556978103829309716465143840700707360411237359984345"
186 | "225161050702705623526601276484830840761183013052793205427462865403603674532865"
187 | "105706587488225698157936789766974220575059683440869735020141020672358502007245"
188 | "225632651341055924019027421624843914035998953539459094407046912091409387001264"
189 | "560016237428802109276457931065792295524988727584610126483699989225695968815920"
190 | "560010165525637567856672279661988578279484885583439751874454551296563443480396"
191 | "642055798293680435220277098429423253302257634180703947699415979159453006975214"
192 | "829336655566156787364005366656416547321704390352132954352916941459904160875320"
193 | "186837937023488868947915107163785290234529244077365949563051007421087142613497"
194 | "459561513849871375704710178795731042296906667021449863746459528082436944578977"
195 | "233004876476524133907592043401963403911473202338071509522201068256342747164602"
196 | "433544005152126693249341967397704159568375355516673027390074972973635496453328"
197 | "886984406119649616277344951827369558822075735517665158985519098666539354948106"
198 | "887320685990754079234240230092590070173196036225475647894064754834664776041146"
199 | "323390565134330684495397907090302346046147096169688688501408347040546074295869"
200 | "913829668246818571031887906528703665083243197440477185567893482308943106828702"
201 | "722809736248093996270607472645539925399442808113736943388729406307926159599546"
202 | "262462970706259484556903471197299640908941805953439325123623550813494900436427"
203 | "852713831591256898929519642728757394691427253436694153236100453730488198551706"
204 | "594121735246258954873016760029886592578662856124966552353382942878542534048308"
205 | "330701653722856355915253478445981831341129001999205981352205117336585640782648"
206 | "494276441137639386692480311836445369858917544264739988228462184490087776977631"
207 | "279572267265556259628254276531830013407092233436577916012809317940171859859993"
208 | "384923549564005709955856113498025249906698423301735035804408116855265311709957"
209 | "089942732870925848789443646005041089226691783525870785951298344172953519537885"
210 | "534573742608590290817651557803905946408735061232261120093731080485485263572282"
211 | "576820341605048466277504500312620080079980492548534694146977516493270950493463"
212 | "938243222718851597405470214828971117779237612257887347718819682546298126868581"
213 | "705074027255026332904497627789442362167411918626943965067151577958675648239939"
214 | "176042601763387045499017614364120469218237076488783419689686118155815873606293"
215 | "860381017121585527266830082383404656475880405138080163363887421637140643549556"
216 | "186896411228214075330265510042410489678352858829024367090488711819090949453314"
217 | "421828766181031007354770549815968077200947469613436092861484941785017180779306"
218 | "810854690009445899527942439813921350558642219648349151263901280383200109773868"
219 | "066287792397180146134324457264009737425700735921003154150893679300816998053652"
220 | "027600727749674584002836240534603726341655425902760183484030681138185510597970"
221 | "566400750942608788573579603732451414678670368809880609716425849759513806930944"
222 | "940151542222194329130217391253835591503100333032511174915696917450271494331515"
223 | "588540392216409722910112903552181576282328318234254832611191280092825256190205"
224 | "263016391147724733148573910777587442538761174657867116941477642144111126358355"
225 | "387136101102326798775641024682403226483464176636980663785768134920453022408197"
226 | "278564719839630878154322116691224641591177673225326433568614618654522268126887"
227 | "268445968442416107854016768142080885028005414361314623082102594173756238994207"
228 | "571362751674573189189456283525704413354375857534269869947254703165661399199968"
229 | "262824727064133622217892390317608542894373393561889165125042440400895271983787"
230 | "386480584726895462438823437517885201439560057104811949884239060613695734231559"
231 | "079670346149143447886360410318235073650277859089757827273130504889398900992391"
232 | "350337325085598265586708924261242947367019390772713070686917092646254842324074"
233 | "855036608013604668951184009366860954632500214585293095000090715105823626729326"
234 | "453738210493872499669933942468551648326113414611068026744663733437534076429402"
235 | "668297386522093570162638464852851490362932019919968828517183953669134522244470"
236 | "804592396602817156551565666111359823112250628905854914509715755390024393153519"
237 | "090210711945730024388017661503527086260253788179751947806101371500448991721002"
238 | "220133501310601639154158957803711779277522597874289191791552241718958536168059"
239 | "474123419339842021874564925644346239253195313510331147639491199507285843065836"
240 | "193536932969928983791494193940608572486396883690326556436421664425760791471086"
241 | "998431573374964883529276932822076294728238153740996154559879825989109371712621"
242 | "828302584811238901196822142945766758071865380650648702613389282299497257453033"
243 | "283896381843944770779402284359883410035838"
244 | )
245 |
246 |
247 | def all_types(values, for_cls):
248 | # "123.456" => "'123.456'" -- "123.456" -- "123456, 1000"
249 | if for_cls in (PyFraction, QFraction):
250 | def int_args(a, b):
251 | return f"{a}, {b}"
252 | def types(v):
253 | f = QFraction(v).as_integer_ratio()
254 | return (
255 | repr(v),
256 | v,
257 | '"%d/%d"' % f,
258 | int_args(f[0], f[1]),
259 | int_args(f[0]*10, f[1]*100),
260 | int_args(f[0]*99, f[1]*f[0] - 1),
261 | int_args(f[0]*64*64, f[1]*1024*1024),
262 | )
263 | else:
264 | def types(v):
265 | return (
266 | repr(v),
267 | v,
268 | )
269 |
270 | return [
271 | tp_v
272 | for v in values
273 | for tp_v in types(v)
274 | ]
275 |
276 |
277 | def run_bm(code, setup='pass', number=200, repeat=1_000, gns=None):
278 | times = timeit.repeat(code, setup, number=number, repeat=repeat, globals=gns)
279 | p10 = len(times) // 10
280 | if p10 >= 1:
281 | times.sort()
282 | times = times[p10:-p10]
283 | return statistics.mean(times) * 1_000_000 # s -> us
284 |
285 |
286 | def bm_pidigits(pi_string=PI_DIGITS, classes=classes):
287 | gns = {}
288 | for cls in classes:
289 | cls_name = cls.__name__
290 | gns[cls_name] = cls
291 | gns['s'] = pi_string
292 | results = {}
293 | code = f"all({cls_name}(s[:i]) for i in range(3, {len(pi_string)}, 265))"
294 | results[code] = run_bm(code, number=30, repeat=1, gns=gns)
295 | yield (cls, results)
296 |
297 |
298 | def bm_create(values=benchmark_values, classes=classes):
299 | gns = {}
300 | for cls in classes:
301 | cls_name = cls.__name__
302 | gns[cls_name] = cls
303 | results = {}
304 | for args in all_types(values, cls):
305 | code = f"{cls_name}({args})"
306 | results[code] = run_bm(code, gns=gns)
307 | yield (cls, results)
308 |
309 |
310 | def bm_calculation(expressions=benchmark_expressions, values=benchmark_values, classes=classes):
311 | gns = {}
312 | for cls in classes:
313 | results = defaultdict(list)
314 | cls_name = cls.__name__
315 | for a, b, c in itertools.product(values, repeat=3):
316 | gns.update(a=cls(a), b=cls(b), c=cls(c))
317 |
318 | for expr in expressions:
319 | results[f"{cls_name}: {expr}"].append(run_bm(expr, number=30, repeat=100, gns=gns))
320 |
321 | # average over all value combinations
322 | results = {expr: statistics.mean(t) for expr, t in results.items()}
323 | yield (cls, results)
324 |
325 |
326 | def run_benchmarks(benchmarks=()):
327 | for name, bm in [
328 | ('create', bm_create),
329 | ('compute', bm_calculation),
330 | ('pidigits', bm_pidigits),
331 | ]:
332 | if benchmarks and name not in benchmarks:
333 | continue
334 | for cls, result in bm():
335 | yield ((name, cls), result)
336 |
337 |
338 | def main(*benchmarks):
339 | results_by_type = defaultdict(dict)
340 | now = datetime.datetime.now
341 | start_time = now()
342 | print(start_time)
343 | for key, res in run_benchmarks(benchmarks):
344 | for code, t in sorted(res.items()):
345 | print(f"{code:50s}: {t:8.2f} us")
346 | results_by_type[key].update(res)
347 | print("++", datetime.timedelta(seconds=(now() - start_time).seconds))
348 |
349 | avg_by_type = defaultdict(dict)
350 | for (what, cls), results in results_by_type.items():
351 | avg_by_type[what][cls] = statistics.mean(results.values())
352 |
353 | for what, timings in sorted(avg_by_type.items(), reverse=True):
354 | print()
355 | print(f"Average times for all '{what}' benchmarks:")
356 | min_avg = min(timings.values())
357 | for cls, avg in sorted(timings.items(), key=operator.itemgetter(1)):
358 | print(f"{cls.__name__:20s}: {avg:8.2f} us ({avg / min_avg:.1f}x)")
359 |
360 |
361 | if __name__ == '__main__':
362 | import sys
363 |
364 | try:
365 | sys.set_int_max_str_digits(len(PI_DIGITS))
366 | except AttributeError:
367 | pass
368 |
369 | import platform
370 | print(f"Running benchmarks with Python {platform.python_version()} on {platform.platform()}")
371 |
372 | main(*sys.argv[1:])
373 |
--------------------------------------------------------------------------------
/src/quicktions.pyx:
--------------------------------------------------------------------------------
1 | # cython: language_level=3
2 | ## cython: profile=True
3 | # cython: freethreading_compatible=True
4 |
5 | # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
6 | # 2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved
7 | #
8 | # Based on the "fractions" module in CPython 3.4+.
9 | # https://hg.python.org/cpython/file/b18288f24501/Lib/fractions.py
10 | #
11 | # Updated to match the recent development in CPython.
12 | # https://github.com/python/cpython/blob/main/Lib/fractions.py
13 | #
14 | # Adapted for efficient Cython compilation by Stefan Behnel.
15 | #
16 |
17 | """
18 | Fast fractions data type for rational numbers.
19 |
20 | This is an almost-drop-in replacement for the standard library's
21 | "fractions.Fraction".
22 | """
23 |
24 | __all__ = ['Fraction']
25 |
26 | __version__ = '1.22'
27 |
28 | cimport cython
29 | from cpython.unicode cimport Py_UNICODE_TODECIMAL
30 | from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE
31 | from cpython.long cimport PyLong_FromString
32 |
33 | cdef extern from *:
34 | cdef long LONG_MAX, INT_MAX
35 | cdef long long PY_LLONG_MIN, PY_LLONG_MAX
36 | cdef long long MAX_SMALL_NUMBER "(PY_LLONG_MAX / 100)"
37 |
38 | cdef object Rational, Integral, Real, Complex, Decimal, math, operator, re
39 | cdef object PY_MAX_ULONGLONG = ( PY_LLONG_MAX) * 2 + 1
40 |
41 | from numbers import Rational, Integral, Real, Complex
42 | from decimal import Decimal
43 | import math
44 | import operator
45 | import re
46 |
47 | cdef object _operator_index = operator.index
48 | cdef object math_gcd = math.gcd
49 |
50 | # Cache widely used 10**x int objects.
51 | # Py3.12/Ubuntu64: sys.getsizeof(tuple[58]) == 512 bytes, tuple[91] == 768, tuple[123] == 1024
52 | DEF CACHED_POW10 = 91
53 |
54 | cdef tuple _cache_pow10():
55 | cdef int i
56 | in_ull = True
57 | l = []
58 | x = 1
59 | for i in range(CACHED_POW10):
60 | l.append(x)
61 | if in_ull:
62 | try:
63 | _C_POW_10[i] = x
64 | except OverflowError:
65 | in_ull = False
66 | x *= 10
67 | return tuple(l)
68 |
69 | cdef unsigned long long[CACHED_POW10] _C_POW_10
70 | cdef tuple POW_10 = _cache_pow10()
71 |
72 |
73 | cdef unsigned long long _c_pow10(Py_ssize_t i):
74 | return _C_POW_10[i]
75 |
76 |
77 | cdef pow10(long long i):
78 | if 0 <= i < CACHED_POW10:
79 | return POW_10[i]
80 | else:
81 | return 10 ** (