Skip to content

Commit 6d721cd

Browse files
committed
+ normalization and rounding
1 parent 658c266 commit 6d721cd

File tree

6 files changed

+41
-40
lines changed

6 files changed

+41
-40
lines changed

Include/codecs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ PyAPI_FUNC(PyObject *) PyCodec_NameReplaceErrors(PyObject *exc);
168168

169169
#ifndef Py_LIMITED_API
170170
PyAPI_DATA(const char *) Py_hexdigits;
171+
PyAPI_DATA(const char *) Py_hexdigits_upper;
171172
#endif
172173

173174
#ifdef __cplusplus

Lib/test/test_float.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,10 @@ def test_format(self):
709709
self.assertEqual(format(x, '>#10x'), ' 0x1.p-10')
710710
self.assertEqual(format(x, '<10x'), '0x1p-10 ')
711711
self.assertEqual(format(x, '<#10x'), '0x1.p-10 ')
712+
x = float.fromhex('0x1.fe12p0')
713+
self.assertEqual(format(x, 'x'), '0x1.fe12p+0')
714+
self.assertEqual(format(x, '.3x'), '0x1.fe1p+0')
715+
self.assertEqual(format(x, '.1x'), '0x1p+1')
712716

713717
# conversion to string should fail
714718
self.assertRaises(ValueError, format, 3.0, "s")

Lib/test/test_format.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ def test_common_format(self):
287287
"%d format: a real number is required, not bytes")
288288
test_exc_common('%x', '1', TypeError,
289289
"%x format: an integer or float is required, not str")
290-
test_exc_common('%i', 1j, TypeError,
291-
"%i format: a real number is required, not complex")
290+
test_exc_common('%d', 1j, TypeError,
291+
"%d format: a real number is required, not complex")
292292

293293
def test_str_format(self):
294294
testformat("%r", "\u0378", "'\\u0378'") # non printable

Python/codecs.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Copyright (c) Corporation for National Research Initiatives.
1616
#include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI
1717

1818
const char *Py_hexdigits = "0123456789abcdef";
19+
const char *Py_hexdigits_upper = "0123456789ABCDEF";
1920

2021
/* --- Codec Registry ----------------------------------------------------- */
2122

Python/pystrtod.c

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -932,55 +932,46 @@ char * PyOS_double_to_string(double val,
932932

933933
/* _Py_dg_dtoa is available. */
934934

935-
/* turn ASCII hex characters into integer values and vice versa */
936-
937-
static char
938-
char_from_hex(int x, int upper)
939-
{
940-
assert(0 <= x && x < 16);
941-
char c = Py_hexdigits[x];
942-
if (upper) {
943-
c = Py_TOUPPER(c);
944-
}
945-
return c;
946-
}
947-
948935
/* convert a float to a hexadecimal string */
949936

950-
/* TOHEX_NBITS is DBL_MANT_DIG rounded up to the next integer
951-
of the form 4k+1. */
952-
#define TOHEX_NBITS DBL_MANT_DIG + 3 - (DBL_MANT_DIG+2)%4
953-
954937
char *
955938
_Py_dg_dtoa_hex(double x, int precision, int always_add_sign,
956939
int use_alt_formatting, int upper)
957940
{
958-
int e, shift, i, si;
941+
int e;
959942
double m = frexp(fabs(x), &e);
960943

944+
if (precision < 0) {
945+
precision = (DBL_MANT_DIG + 2 - (DBL_MANT_DIG+2)%4)/4;
946+
}
947+
961948
if (m) {
962-
/* normalization XXX: valid after rounding? */
963-
shift = 1 - Py_MAX(DBL_MIN_EXP - e, 0);
949+
/* normalization */
950+
int shift = 1 - Py_MAX(DBL_MIN_EXP - e, 0);
964951
m = ldexp(m, shift);
965952
e -= shift;
966-
}
967953

968-
if (precision < 0) {
969-
precision = (TOHEX_NBITS-1)/4;
954+
do {
955+
/* round to precision digits */
956+
double frac = ldexp(m, 4*precision);
957+
frac -= floor(frac);
958+
frac *= 16.0;
959+
m += ldexp(frac >= 8.0, -4*precision);
960+
if ((int)(m) & 0x2) {
961+
m /= 2.0;
962+
e += 1;
963+
}
964+
else {
965+
break;
966+
}
967+
} while (1);
970968
}
971969

972-
/* round to precision digits */
973-
double frac = ldexp(m, 4*precision);
974-
frac -= floor(frac);
975-
frac *= 16.0;
976-
m += ldexp(frac >= 8.0, -4*precision);
977-
978-
/* Space for precision + 1 digits, sign, 0x prefix, a decimal
979-
point, the trailing NUL byte and an exponent. */
980-
char *s = PyMem_Malloc(precision + DBL_MAX_EXP + 7);
970+
/* Allocate space for [±] 0x h. [hhhhhhhh] p± exp '\0' */
971+
char *s = PyMem_Malloc(1 + 2 + 2 + precision + 2 + DBL_MAX_EXP + 1);
981972

982973
/* sign and prefix */
983-
si = 0;
974+
int si = 0;
984975
if (copysign(1.0, x) == -1.0) {
985976
s[si] = '-';
986977
si++;
@@ -995,18 +986,21 @@ _Py_dg_dtoa_hex(double x, int precision, int always_add_sign,
995986
si++;
996987

997988
/* mantissa */
998-
s[si] = char_from_hex((int)m, upper);
989+
const char *hexmap = upper ? Py_hexdigits_upper : Py_hexdigits;
990+
assert(0 <= (int)m < 16);
991+
s[si] = hexmap[(int)m];
999992
si++;
1000993
m -= (int)m;
1001994
s[si] = '.';
1002-
for (i=0; i < precision; i++) {
995+
for (int i = 0; i < precision; i++) {
1003996
si++;
1004997
m *= 16.0;
1005-
s[si] = char_from_hex((int)m, upper);
998+
assert(0 <= (int)m < 16);
999+
s[si] = hexmap[(int)m];
10061000
m -= (int)m;
10071001
}
10081002

1009-
/* clear trailing zeros from mantissa */
1003+
/* clear trailing zeros from mantissa (and maybe the dot) */
10101004
while (s[si] == '0') {
10111005
si--;
10121006
}
@@ -1017,7 +1011,7 @@ _Py_dg_dtoa_hex(double x, int precision, int always_add_sign,
10171011
/* exponent */
10181012
s[si] = upper ? 'P' : 'p';
10191013
si++;
1020-
i = snprintf(s+si, DBL_MAX_EXP+1, "%+d", e);
1014+
int i = snprintf(s+si, DBL_MAX_EXP+1, "%+d", e);
10211015
si += i+1;
10221016

10231017
return PyMem_Realloc(s, si);

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ Python/ast_opt.c fold_unaryop ops -
341341
Python/ceval.c - _PyEval_BinaryOps -
342342
Python/ceval.c - _Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS -
343343
Python/codecs.c - Py_hexdigits -
344+
Python/codecs.c - Py_hexdigits_upper -
344345
Python/codecs.c - ucnhash_capi -
345346
Python/codecs.c _PyCodecRegistry_Init methods -
346347
Python/compile.c - NO_LABEL -

0 commit comments

Comments
 (0)