Skip to content

Commit 94ca8ab

Browse files
warnerjon12skirpichev
authored andcommitted
Support binary/octal/hexadecimal string output
Adapted from #414. Partial fix for #345 Co-authored-by: Sergey B Kirpichev <[email protected]> * this version doesn't touch argument names (dps->ps) * no support for float.hex()-style binary exponents
1 parent a1d9bcb commit 94ca8ab

File tree

2 files changed

+52
-22
lines changed

2 files changed

+52
-22
lines changed

mpmath/libmp/libmpf.py

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
MPZ_TWO, MPZ_ZERO, STRICT, gmpy, sage, sage_utils)
1616
from .libintmath import (bctable, bin_to_radix, giant_steps, isqrt, isqrt_fast,
1717
lshift, numeral, rshift, sqrt_fixed, sqrtrem,
18-
trailing, trailtable)
18+
stddigits, trailing, trailtable)
1919

2020

2121
class ComplexResult(ValueError):
@@ -1028,13 +1028,13 @@ def mpf_perturb(x, eps_sign, prec, rnd):
10281028
# Radix conversion #
10291029
#----------------------------------------------------------------------------#
10301030

1031-
def to_digits_exp(s, dps):
1031+
def to_digits_exp(s, dps, base=10):
10321032
"""Helper function for representing the floating-point number s as
1033-
a decimal with dps digits. Returns (sign, string, exponent) where
1034-
sign is '' or '-', string is the digit string, and exponent is
1035-
the decimal exponent as an int.
1033+
a string with dps digits. Returns (sign, string, exponent) where
1034+
sign is '' or '-', string is the digit string in the given base,
1035+
and exponent is the exponent as an int.
10361036
1037-
If inexact, the decimal representation is rounded toward zero."""
1037+
If inexact, the string representation is rounded toward zero."""
10381038

10391039
# Extract sign first so it doesn't mess up the string digit count
10401040
if s[0]:
@@ -1047,12 +1047,19 @@ def to_digits_exp(s, dps):
10471047
if not man:
10481048
return '', '0', 0
10491049

1050-
bitprec = int(dps * math.log(10,2)) + 10
1050+
if base == 10:
1051+
blog2 = 3.3219280948873626
1052+
elif pow(2, blog2 := int(math.log2(base))) == base:
1053+
pass
1054+
else:
1055+
raise NotImplementedError
1056+
1057+
bitprec = int(dps * blog2) + 10
10511058

10521059
# Cut down to size
10531060
# TODO: account for precision when doing this
10541061
exp_from_1 = exp + bc
1055-
if abs(exp_from_1) > 3500:
1062+
if base == 10 and abs(exp_from_1) > 3500:
10561063
from .libelefun import mpf_ln2, mpf_ln10
10571064

10581065
# Set b = int(exp * log(2)/log(10))
@@ -1073,19 +1080,19 @@ def to_digits_exp(s, dps):
10731080
# fixed-point number and then converting that number to
10741081
# a decimal fixed-point number.
10751082
fixprec = max(bitprec - exp - bc, 0)
1076-
fixdps = int(fixprec / math.log(10,2) + 0.5)
1083+
fixdps = int(fixprec / blog2 + 0.5)
10771084
sf = to_fixed(s, fixprec)
1078-
sd = bin_to_radix(sf, fixprec, 10, fixdps)
1079-
digits = numeral(sd, base=10, size=dps)
1085+
sb = bin_to_radix(sf, fixprec, base, fixdps)
1086+
digits = numeral(sb, base=base, size=dps)
10801087

10811088
exponent += len(digits) - fixdps - 1
10821089
return sign, digits, exponent
10831090

10841091
def to_str(s, dps, strip_zeros=True, min_fixed=None, max_fixed=None,
1085-
show_zero_exponent=False):
1092+
show_zero_exponent=False, base=10):
10861093
"""
1087-
Convert a raw mpf to a decimal floating-point literal with at
1088-
most `dps` decimal digits in the mantissa (not counting extra zeros
1094+
Convert a raw mpf to a floating-point literal in the given base
1095+
with at most `dps` digits in the mantissa (not counting extra zeros
10891096
that may be inserted for visual purposes).
10901097
10911098
The number will be printed in fixed-point format if the position
@@ -1100,13 +1107,15 @@ def to_str(s, dps, strip_zeros=True, min_fixed=None, max_fixed=None,
11001107
by from_str, float() or Decimal().
11011108
"""
11021109

1110+
sep = '@' if base > 10 else 'e'
1111+
11031112
# Special numbers
11041113
if not s[1]:
11051114
if s == fzero:
11061115
if dps: t = '0.0'
11071116
else: t = '.0'
11081117
if show_zero_exponent:
1109-
t += 'e+0'
1118+
t += sep + '+0'
11101119
return t
11111120
if s == finf: return '+inf'
11121121
if s == fninf: return '-inf'
@@ -1118,23 +1127,27 @@ def to_str(s, dps, strip_zeros=True, min_fixed=None, max_fixed=None,
11181127

11191128
# to_digits_exp rounds to floor.
11201129
# This sometimes kills some instances of "...00001"
1121-
sign, digits, exponent = to_digits_exp(s, dps+3)
1130+
sign, digits, exponent = to_digits_exp(s, dps+3, base)
1131+
1132+
rnd_digs = stddigits[(base//2 + base%2):base]
11221133

11231134
# No digits: show only .0; round exponent to nearest
11241135
if not dps:
1125-
if digits[0] in '56789':
1136+
if digits[0] in rnd_digs:
11261137
exponent += 1
11271138
digits = ".0"
11281139

11291140
else:
11301141
# Rounding up kills some instances of "...99999"
1131-
if len(digits) > dps and digits[dps] in '56789':
1142+
if len(digits) > dps and digits[dps] in rnd_digs:
11321143
digits = digits[:dps]
11331144
i = dps - 1
1134-
while i >= 0 and digits[i] == '9':
1145+
dig = stddigits[base-1]
1146+
while i >= 0 and digits[i] == dig:
11351147
i -= 1
11361148
if i >= 0:
1137-
digits = digits[:i] + str(int(digits[i]) + 1) + '0' * (dps - i - 1)
1149+
digits = digits[:i] + stddigits[int(digits[i], base) + 1] + \
1150+
'0' * (dps - i - 1)
11381151
else:
11391152
digits = '1' + '0' * (dps - 1)
11401153
exponent += 1
@@ -1162,9 +1175,19 @@ def to_str(s, dps, strip_zeros=True, min_fixed=None, max_fixed=None,
11621175
if digits[-1] == ".":
11631176
digits += "0"
11641177

1178+
if base == 2:
1179+
prefix = "0b"
1180+
elif base == 8:
1181+
prefix = "0o"
1182+
elif base == 16:
1183+
prefix = "0x"
1184+
else:
1185+
prefix = ""
1186+
1187+
sign += prefix
1188+
11651189
if exponent == 0 and dps and not show_zero_exponent: return sign + digits
1166-
if exponent >= 0: return sign + digits + "e+" + str(exponent)
1167-
if exponent < 0: return sign + digits + "e" + str(exponent)
1190+
return sign + digits + sep + "{:+}".format(exponent)
11681191

11691192
def str_to_man_exp(x, base=10):
11701193
"""Helper function for from_str."""

mpmath/tests/test_convert.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ def test_from_str():
6161
assert mpf(from_str('0b1101.100101')) == mpf('13.578125')
6262
assert mpf(from_str('0o1101.100101')) == mpf('577.12524795532227')
6363

64+
def test_to_str():
65+
assert to_str(from_str('ABC.ABC', base=16), 6, base=16) == '0xabc.abc'
66+
assert to_str(from_str('0x3.a7p10', base=16), 3, base=16) == '0xe9c.0'
67+
assert to_str(from_str('0x1.4ace478p+33'), 7, base=16) == '0x2.959c8f@+8'
68+
assert to_str(from_str('0o1101.100101'), 8, base=8) == '0o1101.1001'
69+
assert to_str(from_str('0b1101.100101'), 10, base=2) == '0b1101.100101'
70+
6471
def test_pretty():
6572
mp.pretty = True
6673
assert repr(mpf(2.5)) == '2.5'

0 commit comments

Comments
 (0)