Files
mpmath--mpmath/mpmath/tests/test_format.py
T
2026-07-13 12:32:53 +08:00

915 lines
34 KiB
Python

import cmath
import ctypes
import math
import sys
import hypothesis.strategies as st
import pytest
from hypothesis import example, given, settings
from mpmath import fp, inf, mp, nan, ninf, workdps
from mpmath.libmp.libmpf import read_format_spec
@st.composite
def fmt_str(draw, types='fFeE', for_complex=False):
res = ''
# fill_char and align
fill_char = draw(st.sampled_from(['']*3 + list('z;clxvjqwer')))
if fill_char:
skip_0_padding = True
if for_complex:
align = draw(st.sampled_from(list('<^>')))
else:
align = draw(st.sampled_from(list('<^>=')))
res += fill_char + align
else:
align = draw(st.sampled_from([''] + list('<^>=')))
if align == '=' and for_complex:
align = ''
if align:
skip_0_padding = True
res += align
else:
skip_0_padding = False
# sign character
res += draw(st.sampled_from([''] + list('-+ ')))
# no_neg_0 (not used yet.)
if sys.version_info >= (3, 11):
res += draw(st.sampled_from([''] + ['z']))
# alternate mode
res += draw(st.sampled_from(['', '#']))
# pad with 0s
pad0 = draw(st.sampled_from(['', '0']))
if pad0 and for_complex:
pad0 = ''
skip_thousand_separators = False
if pad0 and not skip_0_padding:
res += pad0
skip_thousand_separators = True
# Width
res += draw(st.sampled_from(['']*7 + list(map(str, range(1, 40)))
+ ([] if for_complex else ['0' + str(_)
for _ in range(40)])))
# grouping character (thousand_separators)
gchar = draw(st.sampled_from([''] + list(',_')))
if gchar and not skip_thousand_separators:
res += gchar
# Precision
prec = draw(st.sampled_from(['']*7 + list(map(str, range(40)))
+ ['0' + str(_) for _ in range(40)]))
if prec:
res += '.' + prec
if sys.version_info >= (3, 14):
gchar = draw(st.sampled_from([''] + list(',_')))
res += gchar
# Type
res += draw(st.sampled_from(types))
return res
def test_mpf_fmt_cpython():
'''
These tests assure that mpf.__format__ yields the same result as regular
float.__format__, when dps is default.
'''
# 'f' code formatting.
# zeros
assert f'{mp.mpf(0):.0f}' == '0'
assert f'{mp.mpf(0):.1f}' == '0.0'
assert f'{mp.mpf(0):.2f}' == '0.00'
assert f'{mp.mpf(0):.3f}' == '0.000'
assert f'{mp.mpf(0):.50f}' == '0.00000000000000000000000000000000000000000000000000'
# nan, infs
assert f'{inf:f}' == 'inf'
assert f'{inf:+f}' == '+inf'
assert f'{inf:F}' == 'INF'
assert f'{inf:+F}' == '+INF'
assert f'{ninf:f}' == '-inf'
assert f'{ninf:+f}' == '-inf'
assert f'{ninf:F}' == '-INF'
assert f'{ninf:+F}' == '-INF'
assert f'{nan:f}' == 'nan'
assert f'{nan:+f}' == '+nan'
assert f'{nan:F}' == 'NAN'
assert f'{nan:+F}' == '+NAN'
# precision 0; result should never include a .
assert f'{mp.mpf(1.5):.0f}' == '2'
assert f'{mp.mpf(2.5):.0f}' == '2'
assert f'{mp.mpf(3.5):.0f}' == '4'
assert f'{mp.mpf(0.0):.0f}' == '0'
assert f'{mp.mpf(0.1):.0f}' == '0'
assert f'{mp.mpf(0.001):.0f}' == '0'
assert f'{mp.mpf(10.0):.0f}' == '10'
assert f'{mp.mpf(10.1):.0f}' == '10'
assert f'{mp.mpf(10.01):.0f}' == '10'
assert f'{mp.mpf(123.456):.0f}' == '123'
assert f'{mp.mpf(1234.56):.0f}' == '1235'
assert f'{mp.mpf(1e49):.0f}' == '9999999999999999464902769475481793196872414789632'
assert f'{mp.mpf(9.9999999999999987e+49):.0f}' == '99999999999999986860582406952576489172979654066176'
assert f'{mp.mpf(1e50):.0f}' == '100000000000000007629769841091887003294964970946560'
# precision 1
assert f'{mp.mpf(0.0001):.1f}' == '0.0'
assert f'{mp.mpf(0.001):.1f}' == '0.0'
assert f'{mp.mpf(0.01):.1f}' == '0.0'
assert f'{mp.mpf(0.04):.1f}' == '0.0'
assert f'{mp.mpf(0.06):.1f}' == '0.1'
assert f'{mp.mpf(0.25):.1f}' == '0.2'
assert f'{mp.mpf(0.75):.1f}' == '0.8'
assert f'{mp.mpf(1.4):.1f}' == '1.4'
assert f'{mp.mpf(1.5):.1f}' == '1.5'
assert f'{mp.mpf(10.0):.1f}' == '10.0'
assert f'{mp.mpf(1000.03):.1f}' == '1000.0'
assert f'{mp.mpf(1234.5678):.1f}' == '1234.6'
assert f'{mp.mpf(1234.7499):.1f}' == '1234.7'
assert f'{mp.mpf(1234.75):.1f}' == '1234.8'
# precision 2
assert f'{mp.mpf(0.0001):.2f}' == '0.00'
assert f'{mp.mpf(0.001):.2f}' == '0.00'
assert f'{mp.mpf(0.004999):.2f}' == '0.00'
assert f'{mp.mpf(0.005001):.2f}' == '0.01'
assert f'{mp.mpf(0.01):.2f}' == '0.01'
assert f'{mp.mpf(0.125):.2f}' == '0.12'
assert f'{mp.mpf(0.375):.2f}' == '0.38'
assert f'{mp.mpf(1234500):.2f}' == '1234500.00'
assert f'{mp.mpf(1234560):.2f}' == '1234560.00'
assert f'{mp.mpf(1234567):.2f}' == '1234567.00'
assert f'{mp.mpf(1234567.8):.2f}' == '1234567.80'
assert f'{mp.mpf(1234567.89):.2f}' == '1234567.89'
assert f'{mp.mpf(1234567.891):.2f}' == '1234567.89'
assert f'{mp.mpf(1234567.8912):.2f}' == '1234567.89'
# alternate form always includes a decimal point. This only
# makes a difference when the precision is 0.
assert f'{mp.mpf(0):#.0f}' == '0.'
assert f'{mp.mpf(0):#.1f}' == '0.0'
assert f'{mp.mpf(1.5):#.0f}' == '2.'
assert f'{mp.mpf(2.5):#.0f}' == '2.'
assert f'{mp.mpf(10.1):#.0f}' == '10.'
assert f'{mp.mpf(1234.56):#.0f}' == '1235.'
assert f'{mp.mpf(1.4):#.1f}' == '1.4'
assert f'{mp.mpf(0.375):#.2f}' == '0.38'
# if precision is omitted it defaults to 6
assert f'{mp.mpf(0):f}' == '0.000000'
assert f'{mp.mpf(1230000):f}' == '1230000.000000'
assert f'{mp.mpf(1234567):f}' == '1234567.000000'
assert f'{mp.mpf(123.4567):f}' == '123.456700'
assert f'{mp.mpf(1.23456789):f}' == '1.234568'
assert f'{mp.mpf(0.00012):f}' == '0.000120'
assert f'{mp.mpf(0.000123):f}' == '0.000123'
assert f'{mp.mpf(0.00012345):f}' == '0.000123'
assert f'{mp.mpf(0.000001):f}' == '0.000001'
assert f'{mp.mpf(0.0000005001):f}' == '0.000001'
assert f'{mp.mpf(0.0000004999):f}' == '0.000000'
# grouping in fractional part
assert f'{mp.mpf(0.0000004999):.9_f}' == '0.000_000_500'
# 'e' code formatting with explicit precision (>= 0). Output should
# always have exactly the number of places after the point that were
# requested.
# zeros
assert f'{mp.mpf(0):.0e}' == '0e+00'
assert f'{mp.mpf(0):.1e}' == '0.0e+00'
assert f'{mp.mpf(0):.2e}' == '0.00e+00'
assert f'{mp.mpf(0):.10e}' == '0.0000000000e+00'
assert f'{mp.mpf(0):.50e}' == '0.00000000000000000000000000000000000000000000000000e+00'
# nan, infs
assert f'{inf:e}' == 'inf'
assert f'{inf:+e}' == '+inf'
assert f'{inf:E}' == 'INF'
assert f'{inf:+E}' == '+INF'
assert f'{ninf:e}' == '-inf'
assert f'{ninf:+e}' == '-inf'
assert f'{ninf:E}' == '-INF'
assert f'{ninf:+E}' == '-INF'
assert f'{nan:e}' == 'nan'
assert f'{nan:+e}' == '+nan'
assert f'{nan:E}' == 'NAN'
assert f'{nan:+E}' == '+NAN'
# precision 0. no decimal point in the output
assert f'{mp.mpf(0.01):.0e}' == '1e-02'
assert f'{mp.mpf(0.1):.0e}' == '1e-01'
assert f'{mp.mpf(1):.0e}' == '1e+00'
assert f'{mp.mpf(10):.0e}' == '1e+01'
assert f'{mp.mpf(100):.0e}' == '1e+02'
assert f'{mp.mpf(0.012):.0e}' == '1e-02'
assert f'{mp.mpf(0.12):.0e}' == '1e-01'
assert f'{mp.mpf(1.2):.0e}' == '1e+00'
assert f'{mp.mpf(12):.0e}' == '1e+01'
assert f'{mp.mpf(120):.0e}' == '1e+02'
assert f'{mp.mpf(123.456):.0e}' == '1e+02'
assert f'{mp.mpf(0.000123456):.0e}' == '1e-04'
assert f'{mp.mpf(123456000):.0e}' == '1e+08'
assert f'{mp.mpf(0.5):.0e}' == '5e-01'
assert f'{mp.mpf(1.4):.0e}' == '1e+00'
assert f'{mp.mpf(1.5):.0e}' == '2e+00'
assert f'{mp.mpf(1.6):.0e}' == '2e+00'
assert f'{mp.mpf(2.4999999):.0e}' == '2e+00'
assert f'{mp.mpf(2.5):.0e}' == '2e+00'
assert f'{mp.mpf(2.5000001):.0e}' == '3e+00'
assert f'{mp.mpf(3.499999999999):.0e}' == '3e+00'
assert f'{mp.mpf(3.5):.0e}' == '4e+00'
assert f'{mp.mpf(4.5):.0e}' == '4e+00'
assert f'{mp.mpf(5.5):.0e}' == '6e+00'
assert f'{mp.mpf(6.5):.0e}' == '6e+00'
assert f'{mp.mpf(7.5):.0e}' == '8e+00'
assert f'{mp.mpf(8.5):.0e}' == '8e+00'
assert f'{mp.mpf(9.4999):.0e}' == '9e+00'
assert f'{mp.mpf(9.5):.0e}' == '1e+01'
assert f'{mp.mpf(10.5):.0e}' == '1e+01'
assert f'{mp.mpf(14.999):.0e}' == '1e+01'
assert f'{mp.mpf(15):.0e}' == '2e+01'
# precision 1
assert f'{mp.mpf(0.0001):.1e}' == '1.0e-04'
assert f'{mp.mpf(0.001):.1e}' == '1.0e-03'
assert f'{mp.mpf(0.01):.1e}' == '1.0e-02'
assert f'{mp.mpf(0.1):.1e}' == '1.0e-01'
assert f'{mp.mpf(1):.1e}' == '1.0e+00'
assert f'{mp.mpf(10):.1e}' == '1.0e+01'
assert f'{mp.mpf(100):.1e}' == '1.0e+02'
assert f'{mp.mpf(120):.1e}' == '1.2e+02'
assert f'{mp.mpf(123):.1e}' == '1.2e+02'
assert f'{mp.mpf(123.4):.1e}' == '1.2e+02'
# precision 2
assert f'{mp.mpf(0.00013):.2e}' == '1.30e-04'
assert f'{mp.mpf(0.000135):.2e}' == '1.35e-04'
assert f'{mp.mpf(0.0001357):.2e}' == '1.36e-04'
assert f'{mp.mpf(0.0001):.2e}' == '1.00e-04'
assert f'{mp.mpf(0.001):.2e}' == '1.00e-03'
assert f'{mp.mpf(0.01):.2e}' == '1.00e-02'
assert f'{mp.mpf(0.1):.2e}' == '1.00e-01'
assert f'{mp.mpf(1):.2e}' == '1.00e+00'
assert f'{mp.mpf(10):.2e}' == '1.00e+01'
assert f'{mp.mpf(100):.2e}' == '1.00e+02'
assert f'{mp.mpf(1000):.2e}' == '1.00e+03'
assert f'{mp.mpf(1500):.2e}' == '1.50e+03'
assert f'{mp.mpf(1590):.2e}' == '1.59e+03'
assert f'{mp.mpf(1598):.2e}' == '1.60e+03'
assert f'{mp.mpf(1598.7):.2e}' == '1.60e+03'
assert f'{mp.mpf(1598.76):.2e}' == '1.60e+03'
assert f'{mp.mpf(9999):.2e}' == '1.00e+04'
# omitted precision defaults to 6
assert f'{mp.mpf(0):e}' == '0.000000e+00'
assert f'{mp.mpf(165):e}' == '1.650000e+02'
assert f'{mp.mpf(1234567):e}' == '1.234567e+06'
assert f'{mp.mpf(12345678):e}' == '1.234568e+07'
assert f'{mp.mpf(1.1):e}' == '1.100000e+00'
# alternate form always contains a decimal point. This only makes
# a difference when precision is 0.
assert f'{mp.mpf(0.01):#.0e}' == '1.e-02'
assert f'{mp.mpf(0.1):#.0e}' == '1.e-01'
assert f'{mp.mpf(1):#.0e}' == '1.e+00'
assert f'{mp.mpf(10):#.0e}' == '1.e+01'
assert f'{mp.mpf(100):#.0e}' == '1.e+02'
assert f'{mp.mpf(0.012):#.0e}' == '1.e-02'
assert f'{mp.mpf(0.12):#.0e}' == '1.e-01'
assert f'{mp.mpf(1.2):#.0e}' == '1.e+00'
assert f'{mp.mpf(12):#.0e}' == '1.e+01'
assert f'{mp.mpf(120):#.0e}' == '1.e+02'
assert f'{mp.mpf(123.456):#.0e}' == '1.e+02'
assert f'{mp.mpf(0.000123456):#.0e}' == '1.e-04'
assert f'{mp.mpf(123456000):#.0e}' == '1.e+08'
assert f'{mp.mpf(0.5):#.0e}' == '5.e-01'
assert f'{mp.mpf(1.4):#.0e}' == '1.e+00'
assert f'{mp.mpf(1.5):#.0e}' == '2.e+00'
assert f'{mp.mpf(1.6):#.0e}' == '2.e+00'
assert f'{mp.mpf(2.4999999):#.0e}' == '2.e+00'
assert f'{mp.mpf(2.5):#.0e}' == '2.e+00'
assert f'{mp.mpf(2.5000001):#.0e}' == '3.e+00'
assert f'{mp.mpf(3.499999999999):#.0e}' == '3.e+00'
assert f'{mp.mpf(3.5):#.0e}' == '4.e+00'
assert f'{mp.mpf(4.5):#.0e}' == '4.e+00'
assert f'{mp.mpf(5.5):#.0e}' == '6.e+00'
assert f'{mp.mpf(6.5):#.0e}' == '6.e+00'
assert f'{mp.mpf(7.5):#.0e}' == '8.e+00'
assert f'{mp.mpf(8.5):#.0e}' == '8.e+00'
assert f'{mp.mpf(9.4999):#.0e}' == '9.e+00'
assert f'{mp.mpf(9.5):#.0e}' == '1.e+01'
assert f'{mp.mpf(10.5):#.0e}' == '1.e+01'
assert f'{mp.mpf(14.999):#.0e}' == '1.e+01'
assert f'{mp.mpf(15):#.0e}' == '2.e+01'
assert f'{mp.mpf(123.4):#.1e}' == '1.2e+02'
assert f'{mp.mpf(0.0001357):#.2e}' == '1.36e-04'
# 'g' code formatting.
# zeros
assert f'{mp.mpf(0):.0g}' == '0'
assert f'{mp.mpf(0):.1g}' == '0'
assert f'{mp.mpf(0):.2g}' == '0'
assert f'{mp.mpf(0):.3g}' == '0'
assert f'{mp.mpf(0):.4g}' == '0'
assert f'{mp.mpf(0):.10g}' == '0'
assert f'{mp.mpf(0):.50g}' == '0'
assert f'{mp.mpf(0):.100g}' == '0'
# nan, infs
assert f'{inf:g}' == 'inf'
assert f'{inf:+g}' == '+inf'
assert f'{inf:G}' == 'INF'
assert f'{inf:+G}' == '+INF'
assert f'{ninf:g}' == '-inf'
assert f'{ninf:+g}' == '-inf'
assert f'{ninf:G}' == '-INF'
assert f'{ninf:+G}' == '-INF'
assert f'{nan:g}' == 'nan'
assert f'{nan:+g}' == '+nan'
assert f'{nan:G}' == 'NAN'
assert f'{nan:+G}' == '+NAN'
# precision 0 doesn't make a lot of sense for the 'g' code (what does
# it mean to have no significant digits?); in practice, it's interpreted
# as identical to precision 1
assert f'{mp.mpf(1000):.0g}' == '1e+03'
assert f'{mp.mpf(100):.0g}' == '1e+02'
assert f'{mp.mpf(10):.0g}' == '1e+01'
assert f'{mp.mpf(1):.0g}' == '1'
assert f'{mp.mpf(0.1):.0g}' == '0.1'
assert f'{mp.mpf(0.01):.0g}' == '0.01'
assert f'{mp.mpf(1e-3):.0g}' == '0.001'
assert f'{mp.mpf(1e-4):.0g}' == '0.0001'
assert f'{mp.mpf(1e-5):.0g}' == '1e-05'
assert f'{mp.mpf(1e-6):.0g}' == '1e-06'
assert f'{mp.mpf(12):.0g}' == '1e+01'
assert f'{mp.mpf(120):.0g}' == '1e+02'
assert f'{mp.mpf(1.2):.0g}' == '1'
assert f'{mp.mpf(0.12):.0g}' == '0.1'
assert f'{mp.mpf(0.012):.0g}' == '0.01'
assert f'{mp.mpf(0.0012):.0g}' == '0.001'
assert f'{mp.mpf(0.00012):.0g}' == '0.0001'
assert f'{mp.mpf(0.000012):.0g}' == '1e-05'
assert f'{mp.mpf(0.0000012):.0g}' == '1e-06'
# precision 1 identical to precision 0
assert f'{mp.mpf(1000):.1g}' == '1e+03'
assert f'{mp.mpf(100):.1g}' == '1e+02'
assert f'{mp.mpf(10):.1g}' == '1e+01'
assert f'{mp.mpf(1):.1g}' == '1'
assert f'{mp.mpf(0.1):.1g}' == '0.1'
assert f'{mp.mpf(0.01):.1g}' == '0.01'
assert f'{mp.mpf(1e-3):.1g}' == '0.001'
assert f'{mp.mpf(1e-4):.1g}' == '0.0001'
assert f'{mp.mpf(1e-5):.1g}' == '1e-05'
assert f'{mp.mpf(1e-6):.1g}' == '1e-06'
assert f'{mp.mpf(12):.1g}' == '1e+01'
assert f'{mp.mpf(120):.1g}' == '1e+02'
assert f'{mp.mpf(1.2):.1g}' == '1'
assert f'{mp.mpf(0.12):.1g}' == '0.1'
assert f'{mp.mpf(0.012):.1g}' == '0.01'
assert f'{mp.mpf(0.0012):.1g}' == '0.001'
assert f'{mp.mpf(0.00012):.1g}' == '0.0001'
assert f'{mp.mpf(0.000012):.1g}' == '1e-05'
assert f'{mp.mpf(0.0000012):.1g}' == '1e-06'
# precision 2
assert f'{mp.mpf(1000):.2g}' == '1e+03'
assert f'{mp.mpf(100):.2g}' == '1e+02'
assert f'{mp.mpf(10):.2g}' == '10'
assert f'{mp.mpf(1):.2g}' == '1'
assert f'{mp.mpf(0.1):.2g}' == '0.1'
assert f'{mp.mpf(0.01):.2g}' == '0.01'
assert f'{mp.mpf(0.001):.2g}' == '0.001'
assert f'{mp.mpf(1e-4):.2g}' == '0.0001'
assert f'{mp.mpf(1e-5):.2g}' == '1e-05'
assert f'{mp.mpf(1e-6):.2g}' == '1e-06'
assert f'{mp.mpf(1234):.2g}' == '1.2e+03'
assert f'{mp.mpf(123):.2g}' == '1.2e+02'
assert f'{mp.mpf(12.3):.2g}' == '12'
assert f'{mp.mpf(1.23):.2g}' == '1.2'
assert f'{mp.mpf(0.123):.2g}' == '0.12'
assert f'{mp.mpf(0.0123):.2g}' == '0.012'
assert f'{mp.mpf(0.00123):.2g}' == '0.0012'
assert f'{mp.mpf(0.000123):.2g}' == '0.00012'
assert f'{mp.mpf(0.0000123):.2g}' == '1.2e-05'
# bad cases from http://bugs.python.org/issue9980
assert f'{mp.mpf(38210.0):.12g}' == '38210'
assert f'{mp.mpf(37210.0):.12g}' == '37210'
assert f'{mp.mpf(36210.0):.12g}' == '36210'
# alternate g formatting: always include decimal point and
# exactly <precision> significant digits.
assert f'{mp.mpf(0):#.0g}' == '0.'
assert f'{mp.mpf(0):#.1g}' == '0.'
assert f'{mp.mpf(0):#.2g}' == '0.0'
assert f'{mp.mpf(0):#.3g}' == '0.00'
assert f'{mp.mpf(0):#.4g}' == '0.000'
assert f'{mp.mpf(0.2):#.0g}' == '0.2'
assert f'{mp.mpf(0.2):#.1g}' == '0.2'
assert f'{mp.mpf(0.2):#.2g}' == '0.20'
assert f'{mp.mpf(0.2):#.3g}' == '0.200'
assert f'{mp.mpf(0.2):#.4g}' == '0.2000'
assert f'{mp.mpf(0.2):#.10g}' == '0.2000000000'
assert f'{mp.mpf(2):#.0g}' == '2.'
assert f'{mp.mpf(2):#.1g}' == '2.'
assert f'{mp.mpf(2):#.2g}' == '2.0'
assert f'{mp.mpf(2):#.3g}' == '2.00'
assert f'{mp.mpf(2):#.4g}' == '2.000'
assert f'{mp.mpf(20):#.0g}' == '2.e+01'
assert f'{mp.mpf(20):#.1g}' == '2.e+01'
assert f'{mp.mpf(20):#.2g}' == '20.'
assert f'{mp.mpf(20):#.3g}' == '20.0'
assert f'{mp.mpf(20):#.4g}' == '20.00'
assert f'{mp.mpf(234.56):#.0g}' == '2.e+02'
assert f'{mp.mpf(234.56):#.1g}' == '2.e+02'
assert f'{mp.mpf(234.56):#.2g}' == '2.3e+02'
assert f'{mp.mpf(234.56):#.3g}' == '235.'
assert f'{mp.mpf(234.56):#.4g}' == '234.6'
assert f'{mp.mpf(234.56):#.5g}' == '234.56'
assert f'{mp.mpf(234.56):#.6g}' == '234.560'
# '%' code formatting.
# nan, infs
assert f'{inf:%}' == 'inf%'
assert f'{inf:+%}' == '+inf%'
assert f'{ninf:%}' == '-inf%'
assert f'{ninf:+%}' == '-inf%'
assert f'{nan:%}' == 'nan%'
assert f'{nan:+%}' == '+nan%'
# No formatting code.
assert f'{mp.mpf(0.0):.0}' == '0e+00'
assert f'{mp.pi}' == '3.14159265358979'
mp.pretty_dps = 'repr'
assert f'{mp.pi}' == '3.1415926535897931'
@given(fmt_str(types=list('fFeEgG%') + ['']),
st.floats(allow_nan=True,
allow_infinity=True,
allow_subnormal=True))
@example(fmt='.0g', x=9.995074823339339e-05) # issue 880
@example(fmt='.016f', x=0.1) # issue 915
@example(fmt='0030f', x=0.3)
@example(fmt='0=13,f', x=1.1) # issue 917
@example(fmt='013,f', x=1.1)
@example(fmt='013,.0%', x=1.1)
@example(fmt='010.6,f', x=0.1234567891)
@example(fmt='010.7,f', x=0.1234567891)
@example(fmt='010._f', x=0.1234567891)
def test_mpf_floats_bulk(fmt, x):
'''
These are additional random tests that check that mp.mpf and fp.mpf yield
the same results for default precision.
'''
mp.pretty_dps = "repr"
if not x and math.copysign(1, x) == -1:
return # skip negative zero
spec = read_format_spec(fmt)
if spec['frac_separators'] and sys.version_info < (3, 14):
mp.pretty_dps = "str"
return # see also python/cpython#130860
if not spec['type'] and spec['precision'] < 0 and math.isfinite(x):
# The mpmath could choose a different decimal
# representative (wrt CPython) for same binary
# floating-point number.
assert float(format(x)) == float(format(mp.mpf(x)))
else:
if spec['type'] == '%' and math.isinf(100*x):
return # mpf can't overflow
assert format(x, fmt) == format(mp.mpf(x), fmt)
@given(fmt_str(types=list('gGfFeE') + [''], for_complex=True),
st.complex_numbers(allow_nan=True,
allow_infinity=True,
allow_subnormal=True))
def test_mpc_complexes(fmt, z):
mp.pretty_dps = "repr"
if ((not z.real and math.copysign(1, z.real) == -1)
or (not z.imag and math.copysign(1, z.imag) == -1)):
return # skip negative zero
spec = read_format_spec(fmt)
if spec['frac_separators'] and sys.version_info < (3, 14):
return # see also python/cpython#130860
if spec['precision'] < 0 and any(math.isfinite(_) for _ in [z.real, z.imag]):
# The mpmath could choose a different decimal
# representative (wrt CPython) for same binary
# floating-point number.
if cmath.isnan(complex(format(z))):
assert cmath.isnan(complex(format(mp.mpc(z))))
else:
assert complex(format(z)) == complex(format(mp.mpc(z)))
else:
assert format(z, fmt) == format(mp.mpc(z), fmt)
def test_mpc_fmt():
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):=10f}')
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):010f}')
pytest.raises(ValueError, lambda: f'{mp.mpc(1j):%}')
assert f'{1+1j:.0g}' == f'{mp.mpc(1+1j):.0g}'
assert f'{1+1.1j:.2g}' == f'{mp.mpc(1+1.1j):.2g}'
def test_mpf_fmt():
'''
These tests are either specific tests to mpf, or tests that cover
code that is not covered in the CPython tests.
'''
with workdps(1000):
# Numbers with more than 15 significant digits
# fixed format
assert f"{mp.mpf('1.234567890123456789'):.20f}" == '1.23456789012345678900'
assert f"{mp.mpf('1.234567890123456789'):.25f}" == '1.2345678901234567890000000'
assert f"{mp.mpf('1.234567890123456789'):.30f}" == '1.234567890123456789000000000000'
assert f"{mp.mpf('1e-50'):.50f}" == '0.00000000000000000000000000000000000000000000000001'
# scientific notation
assert f"{mp.mpf('1.234567890123456789'):.20e}" == '1.23456789012345678900e+00'
assert f"{mp.mpf('1.234567890123456789'):.25e}" == '1.2345678901234567890000000e+00'
assert f"{mp.mpf('1.234567890123456789'):.30e}" == '1.234567890123456789000000000000e+00'
assert f"{mp.mpf('1e-50'):.50e}" == '1.00000000000000000000000000000000000000000000000000e-50'
# width and fill char
assert f"{mp.mpf('0.01'):z<10.5f}" == '0.01000zzz'
assert f"{mp.mpf('0.01'):z^10.5f}" == 'z0.01000zz'
assert f"{mp.mpf('0.01'):z>10.5f}" == 'zzz0.01000'
assert f"{mp.mpf('0.01'):z=10.5f}" == 'zzz0.01000'
assert f"{mp.mpf('0.01'):z<+10.5f}" == '+0.01000zz'
assert f"{mp.mpf('0.01'):z^+10.5f}" == 'z+0.01000z'
assert f"{mp.mpf('0.01'):z>+10.5f}" == 'zz+0.01000'
assert f"{mp.mpf('0.01'):z=+10.5f}" == '+zz0.01000'
assert f"{mp.mpf('-0.01'):z<10.5f}" == '-0.01000zz'
assert f"{mp.mpf('-0.01'):z^10.5f}" == 'z-0.01000z'
assert f"{mp.mpf('-0.01'):z>10.5f}" == 'zz-0.01000'
assert f"{mp.mpf('-0.01'):z=10.5f}" == '-zz0.01000'
assert f"{mp.mpf('0.01'):z<15.5e}" == '1.00000e-02zzzz'
assert f"{mp.mpf('0.01'):z^15.5e}" == 'zz1.00000e-02zz'
assert f"{mp.mpf('0.01'):z>15.5e}" == 'zzzz1.00000e-02'
assert f"{mp.mpf('0.01'):z=15.5e}" == 'zzzz1.00000e-02'
assert f"{mp.mpf('0.01'):z<+15.5e}" == '+1.00000e-02zzz'
assert f"{mp.mpf('0.01'):z^+15.5e}" == 'z+1.00000e-02zz'
assert f"{mp.mpf('0.01'):z>+15.5e}" == 'zzz+1.00000e-02'
assert f"{mp.mpf('0.01'):z=+15.5e}" == '+zzz1.00000e-02'
assert f"{mp.mpf('-0.01'):z<15.5e}" == '-1.00000e-02zzz'
assert f"{mp.mpf('-0.01'):z^15.5e}" == 'z-1.00000e-02zz'
assert f"{mp.mpf('-0.01'):z>15.5e}" == 'zzz-1.00000e-02'
assert f"{mp.mpf('-0.01'):z=15.5e}" == '-zzz1.00000e-02'
# capitalized scientific notation
assert f"{mp.mpf('-0.01'):z<15.5E}" == '-1.00000E-02zzz'
# generalized format
assert f"{mp.mpf('1.234567890123456789'):.20g}" == '1.234567890123456789'
assert f"{mp.mpf('1.234567890123456789'):.25g}" == '1.234567890123456789'
assert f"{mp.mpf('1.234567890123456789'):.30g}" == '1.234567890123456789'
assert f"{mp.mpf('1e-50'):.50g}" == '1e-50'
assert f"{mp.mpf('1e-50'):.50G}" == '1E-50'
assert f"{mp.mpf('1e-51'):}" == '1e-51'
# thousands separator
assert f"{mp.mpf('1e9'):,.0f}" == '1,000,000,000'
assert f"{mp.mpf('123456789.0123456'):,.4f}" == '123,456,789.0123'
assert f"{mp.mpf('1234567890.123456'):_.4f}" == '1_234_567_890.1235'
assert f"{mp.mpf('1234.5678'):_.4f}" == '1_234.5678'
assert f"{mp.mpf('1e9'):,.0e}" == '1e+09'
assert f"{mp.mpf('123456789.0123456'):,.4e}" == '1.2346e+08'
assert f"{mp.mpf('1234567890.123456'):_.4e}" == '1.2346e+09'
assert f"{mp.mpf('1234.5678'):_.4e}" == '1.2346e+03'
# Tests for no_neg_0
assert f"{mp.mpf('-1e-4'):,.2f}" == '-0.00'
assert f"{mp.mpf('-1e-4'):z,.2f}" == '0.00'
# Tests for = alignment
assert f"{mp.mpf('0.24'):=+20.2f}" == '+ 0.24'
assert f"{mp.mpf('0.24'):=+020.2e}" == '+000000000002.40e-01'
assert f"{mp.mpf('0.24'):=+020.2g}" == '+0000000000000000.24'
# Tests for different kinds of rounding
num = mp.mpf('-1.23456789999901234567')
assert f"{num:=.2Uf}" == "-1.23"
assert f"{num:=.2Df}" == "-1.24"
assert f"{num:=.2Zf}" == "-1.23"
assert f"{num:=.2Nf}" == "-1.23"
assert f"{num:=.2Yf}" == "-1.24"
assert f"{num:=.3Uf}" == "-1.234"
assert f"{num:=.3Df}" == "-1.235"
assert f"{num:=.3Zf}" == "-1.234"
assert f"{num:=.3Nf}" == "-1.235"
assert f"{num:=.3Yf}" == "-1.235"
assert f"{num:=.10Uf}" == "-1.2345678999"
assert f"{num:=.10Df}" == "-1.2345679000"
assert f"{num:=.10Zf}" == "-1.2345678999"
assert f"{num:=.10Nf}" == "-1.2345679000"
assert f"{num:=.10Yf}" == "-1.2345679000"
num = mp.mpf('1.23456789999901234567')
assert f"{num:=.2Uf}" == "1.24"
assert f"{num:=.2Df}" == "1.23"
assert f"{num:=.2Zf}" == "1.23"
assert f"{num:=.2Nf}" == "1.23"
assert f"{num:=.2Yf}" == "1.24"
assert f"{num:=.3Uf}" == "1.235"
assert f"{num:=.3Df}" == "1.234"
assert f"{num:=.3Zf}" == "1.234"
assert f"{num:=.3Nf}" == "1.235"
assert f"{num:=.3Yf}" == "1.235"
assert f"{num:=.10Uf}" == "1.2345679000"
assert f"{num:=.10Df}" == "1.2345678999"
assert f"{num:=.10Zf}" == "1.2345678999"
assert f"{num:=.10Nf}" == "1.2345679000"
assert f"{num:=.10Yf}" == "1.2345679000"
num = mp.mpf('-123.456789999901234567')
assert f"{num:=.2Ue}" == "-1.23e+02"
assert f"{num:=.2De}" == "-1.24e+02"
assert f"{num:=.2Ze}" == "-1.23e+02"
assert f"{num:=.2Ne}" == "-1.23e+02"
assert f"{num:=.2Ye}" == "-1.24e+02"
assert f"{num:=.3Ue}" == "-1.234e+02"
assert f"{num:=.3De}" == "-1.235e+02"
assert f"{num:=.3Ze}" == "-1.234e+02"
assert f"{num:=.3Ne}" == "-1.235e+02"
assert f"{num:=.3Ye}" == "-1.235e+02"
assert f"{num:=.10Ue}" == "-1.2345678999e+02"
assert f"{num:=.10De}" == "-1.2345679000e+02"
assert f"{num:=.10Ze}" == "-1.2345678999e+02"
assert f"{num:=.10Ne}" == "-1.2345679000e+02"
assert f"{num:=.10Ye}" == "-1.2345679000e+02"
num = mp.mpf('123456.789999901234567')
assert f"{num:=.2Ue}" == "1.24e+05"
assert f"{num:=.2De}" == "1.23e+05"
assert f"{num:=.2Ze}" == "1.23e+05"
assert f"{num:=.2Ne}" == "1.23e+05"
assert f"{num:=.2Ye}" == "1.24e+05"
assert f"{num:=.3Ue}" == "1.235e+05"
assert f"{num:=.3De}" == "1.234e+05"
assert f"{num:=.3Ze}" == "1.234e+05"
assert f"{num:=.3Ne}" == "1.235e+05"
assert f"{num:=.3Ye}" == "1.235e+05"
assert f"{num:=.10Ue}" == "1.2345679000e+05"
assert f"{num:=.10De}" == "1.2345678999e+05"
assert f"{num:=.10Ze}" == "1.2345678999e+05"
assert f"{num:=.10Ne}" == "1.2345679000e+05"
assert f"{num:=.10Ye}" == "1.2345679000e+05"
assert f"{mp.mpf('123.456'):.2Ug}" == "1.3e+02"
assert f"{mp.mpf('123.456'):.2Dg}" == "1.2e+02"
assert f"{mp.mpf('123.456'):.2Zg}" == "1.2e+02"
assert f"{mp.mpf('123.456'):.2Ng}" == "1.2e+02"
assert f"{mp.mpf('123.456'):.2Yg}" == "1.3e+02"
assert f"{mp.mpf('-123.456'):.2Ug}" == "-1.2e+02"
assert f"{mp.mpf('-123.456'):.2Dg}" == "-1.3e+02"
assert f"{mp.mpf('-123.456'):.2Zg}" == "-1.2e+02"
assert f"{mp.mpf('-123.456'):.2Ng}" == "-1.2e+02"
assert f"{mp.mpf('-123.456'):.2Yg}" == "-1.3e+02"
assert f"{mp.mpf('123.456'):.5Ug}" == "123.46"
assert f"{mp.mpf('123.456'):.5Dg}" == "123.45"
assert f"{mp.mpf('123.456'):.5Zg}" == "123.45"
assert f"{mp.mpf('123.456'):.5Ng}" == "123.46"
assert f"{mp.mpf('123.456'):.5Yg}" == "123.46"
assert f"{mp.mpf('-123.456'):.5Ug}" == "-123.45"
assert f"{mp.mpf('-123.456'):.5Dg}" == "-123.46"
assert f"{mp.mpf('-123.456'):.5Zg}" == "-123.45"
assert f"{mp.mpf('-123.456'):.5Ng}" == "-123.46"
assert f"{mp.mpf('-123.456'):.5Yg}" == "-123.46"
# Special cases were tying is relevant (cases involve exact floats)
assert f"{mp.mpf('0.25'):.1Nf}" == "0.2"
assert f"{mp.mpf('0.75'):.1Nf}" == "0.8"
num = mp.mpf('0.1')
assert f"{-num:=.2Df}" == "-0.11"
assert f"{-num:=.3Df}" == "-0.101"
assert f"{-num:=.4Df}" == "-0.1001"
assert f"{-num:=.5Df}" == "-0.10001"
assert f"{-num:=.6Df}" == "-0.100001"
assert f"{-num:=.7Df}" == "-0.1000001"
assert f"{-num:=.2De}" == "-1.01e-01"
assert f"{-num:=.3De}" == "-1.001e-01"
assert f"{-num:=.4De}" == "-1.0001e-01"
assert f"{-num:=.5De}" == "-1.00001e-01"
assert f"{-num:=.6De}" == "-1.000001e-01"
assert f"{-num:=.7De}" == "-1.0000001e-01"
assert f"{num:=.2Uf}" == "0.11"
assert f"{num:=.3Uf}" == "0.101"
assert f"{num:=.4Uf}" == "0.1001"
assert f"{num:=.5Uf}" == "0.10001"
assert f"{num:=.6Uf}" == "0.100001"
assert f"{num:=.7Uf}" == "0.1000001"
assert f"{num:=.2Ue}" == "1.01e-01"
assert f"{num:=.3Ue}" == "1.001e-01"
assert f"{num:=.4Ue}" == "1.0001e-01"
assert f"{num:=.5Ue}" == "1.00001e-01"
assert f"{num:=.6Ue}" == "1.000001e-01"
assert f"{num:=.7Ue}" == "1.0000001e-01"
num = mp.mpf('0.25')
assert f"{-num:=.2Df}" == "-0.25"
assert f"{-num:=.3Df}" == "-0.250"
assert f"{-num:=.4Df}" == "-0.2500"
assert f"{-num:=.5Df}" == "-0.25000"
assert f"{-num:=.6Df}" == "-0.250000"
assert f"{-num:=.7Df}" == "-0.2500000"
assert f"{-num:=.2De}" == "-2.50e-01"
assert f"{-num:=.3De}" == "-2.500e-01"
assert f"{-num:=.4De}" == "-2.5000e-01"
assert f"{-num:=.5De}" == "-2.50000e-01"
assert f"{-num:=.6De}" == "-2.500000e-01"
assert f"{-num:=.7De}" == "-2.5000000e-01"
assert f"{num:=.2Uf}" == "0.25"
assert f"{num:=.3Uf}" == "0.250"
assert f"{num:=.4Uf}" == "0.2500"
assert f"{num:=.5Uf}" == "0.25000"
assert f"{num:=.6Uf}" == "0.250000"
assert f"{num:=.7Uf}" == "0.2500000"
assert f"{num:=.2Ue}" == "2.50e-01"
assert f"{num:=.3Ue}" == "2.500e-01"
assert f"{num:=.4Ue}" == "2.5000e-01"
assert f"{num:=.5Ue}" == "2.50000e-01"
assert f"{num:=.6Ue}" == "2.500000e-01"
assert f"{num:=.7Ue}" == "2.5000000e-01"
# Changing the work precision changes the number printed (This is
# expected behavior)
with mp.workdps(20):
assert f"{mp.mpf('0.1'):=.4Uf}" == "0.1000"
assert f"{mp.mpf('-0.1'):=.4Df}" == "-0.1000"
def test_default_rounding():
x = mp.mpf(mp.pi)
assert f"{x:.3f}" == '3.142'
mp.rounding = 'd'
assert f"{x:.3f}" == '3.141'
mp.rounding = 'u'
assert f"{x:.3f}" == '3.142'
def test_issue_858():
for n in range(2, 15):
str_num = '0.' + (n)*'9'
fmt_str = '.' + str(n-1) + 'f'
assert format(mp.mpf(str_num), fmt_str) == format(fp.mpf(str_num), fmt_str)
assert format(mp.mpf(0.96875), '#.0g') == '1.'
def test_errors():
with pytest.raises(ValueError):
# wrong format type
f"{mp.mpf('-4'):22.15k}"
with pytest.raises(ValueError, match="Invalid format specifier '<z15.e'"):
# no precision specified after .
f"{mp.mpf('-0.01'):<z15.e}"
with pytest.raises(ValueError, match="Invalid format specifier '10.5fk'"):
f"{mp.mpf('4'):10.5fk}"
with pytest.raises(ValueError, match="Invalid format specifier '12.3 E '"):
f"{mp.mpf('4'):12.3 E }"
with pytest.raises(ValueError):
f"{mp.mpf(1):.f}"
with pytest.raises(ValueError):
f"{mp.mpf(1):._6f}"
@given(st.floats(allow_nan=True, allow_infinity=True,
allow_subnormal=False))
@example(float('nan'))
@example(float('inf'))
def test_hexadecimal_bulk(x):
if math.isnan(x):
assert math.isnan(float.fromhex(f"{mp.mpf(x):a}"))
else:
assert float.fromhex(f"{mp.mpf(x):a}") == x
try:
libc = ctypes.CDLL("libc.so.6")
except OSError:
libc = None
def float_print(d, i):
fmt = "%." + str(i) + "a\n"
a = ctypes.create_string_buffer(256)
libc.sprintf.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
libc.sprintf(a, bytes(fmt, 'utf-8'), ctypes.c_double(d))
return a.raw.decode('utf-8').split("\n")[0]
@pytest.mark.skipif(libc is None, reason='requires libc')
@given(st.floats(allow_nan=False, allow_infinity=False,
allow_subnormal=False),
st.integers(min_value=0, max_value=15))
def test_hexadecimal_with_libc_bulk(x, p):
fmt = '.' + str(p) + 'a'
x_hex = float_print(x, p)
m_hex = format(mp.mpf(x), fmt)
assert mp.mpf(m_hex) == mp.mpf(x_hex)
@given(st.floats(allow_nan=False, allow_infinity=False,
allow_subnormal=False),
st.integers(min_value=-3, max_value=15))
def test_binary_with_gmpy2_bulk(x, p):
gmpy2 = pytest.importorskip('gmpy2')
if not x and math.copysign(1, x) == -1:
return # skip negative zero
if p >= 0:
fmt = '.' + str(p) + 'b'
else:
fmt = 'b'
g_bin = format(gmpy2.mpfr(x), fmt)
m_bin = format(mp.mpf(x), fmt)
assert m_bin == g_bin
def test_binary_fmt():
x = mp.mpf(3)
assert f'{x:b}' == '1.1p+1'
assert f'{x:.2b}' == '1.10p+1'
assert f'{x:+.2b}' == '+1.10p+1'
assert f'{x:#.2b}' == '1.10p+1'
assert f'{x:.0b}' == '1p+2'
assert f'{x:#.0b}' == '1.p+2'
x = mp.mpf(0)
assert f'{x:.2b}' == '0.00p+0'
assert f'{x:b}' == '0p+0'
def test_hexadecimal_fmt():
with workdps(1000):
x = mp.mpf('1.234567890123456789')
assert f'{x:.20a}' == '0x1.3c0ca428c59fb71a4194p+0'
assert f'{x:.20A}' == '0X1.3C0CA428C59FB71A4194P+0'
assert f'{x:.0a}' == '0x1p+0'
assert f'{x:#.0a}' == '0x1.p+0'
assert f"{mp.mpf('1.234567890123456789'):+.0a}" == '+0x1p+0'