Skip to content

Commit 4e99bb5

Browse files
committed
Faster BCD into integer parsing
1 parent 04a34c3 commit 4e99bb5

File tree

1 file changed

+64
-8
lines changed

1 file changed

+64
-8
lines changed

ext/bcmath/libbcmath/src/recmul.c

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,68 @@ static inline void bc_digits_adjustment(BC_UINT_T *prod_uint, size_t prod_arr_si
5858
}
5959
}
6060

61+
/* This is based on the technique described in https://kholdstare.github.io/technical/2020/05/26/faster-integer-parsing.html.
62+
* This function transforms AABBCCDD into 1000 * AA + 100 * BB + 10 * CC + DD,
63+
* with the caveat that all components must be in the interval [0, 25] to prevent overflow
64+
* due to the multiplication by power of 10 (10 * 25 = 250 is the largest number that fits in a byte).
65+
* The advantage of this method instead of using shifts + 3 multiplications is that this is cheaper
66+
* due to its divide-and-conquer nature.
67+
*/
68+
#if SIZEOF_SIZE_T == 4
69+
static uint32_t bc_parse_chunk_chars(const char *str)
70+
{
71+
uint32_t tmp;
72+
memcpy(&tmp, str, sizeof(tmp));
73+
#if !BC_LITTLE_ENDIAN
74+
tmp = BC_BSWAP(tmp);
75+
#endif
76+
77+
uint32_t lower_digits = (tmp & 0x0f000f00) >> 8;
78+
uint32_t upper_digits = (tmp & 0x000f000f) * 10;
79+
80+
tmp = lower_digits + upper_digits;
81+
82+
lower_digits = (tmp & 0x00ff0000) >> 16;
83+
upper_digits = (tmp & 0x000000ff) * 100;
84+
85+
return lower_digits + upper_digits;
86+
}
87+
#elif SIZEOF_SIZE_T == 8
88+
static uint64_t bc_parse_chunk_chars(const char *str)
89+
{
90+
uint64_t tmp;
91+
memcpy(&tmp, str, sizeof(tmp));
92+
#if !BC_LITTLE_ENDIAN
93+
tmp = BC_BSWAP(tmp);
94+
#endif
95+
96+
uint64_t lower_digits = (tmp & 0x0f000f000f000f00) >> 8;
97+
uint64_t upper_digits = (tmp & 0x000f000f000f000f) * 10;
98+
99+
tmp = lower_digits + upper_digits;
100+
101+
lower_digits = (tmp & 0x00ff000000ff0000) >> 16;
102+
upper_digits = (tmp & 0x000000ff000000ff) * 100;
103+
104+
tmp = lower_digits + upper_digits;
105+
106+
lower_digits = (tmp & 0x0000ffff00000000) >> 32;
107+
upper_digits = (tmp & 0x000000000000ffff) * 10000;
108+
109+
return lower_digits + upper_digits;
110+
}
111+
#endif
112+
61113
/*
62114
* Converts BCD to uint, going backwards from pointer n by the number of
63115
* characters specified by len.
64116
*/
65117
static inline BC_UINT_T bc_partial_convert_to_uint(const char *n, size_t len)
66118
{
119+
if (len == BC_MUL_UINT_DIGITS) {
120+
return bc_parse_chunk_chars(n - BC_MUL_UINT_DIGITS + 1);
121+
}
122+
67123
BC_UINT_T num = 0;
68124
BC_UINT_T base = 1;
69125

@@ -226,14 +282,14 @@ static void bc_standard_mul(bc_num n1, size_t n1len, bc_num n2, size_t n2len, bc
226282
char *pend = pptr + prodlen - 1;
227283
i = 0;
228284
while (i < prod_arr_size - 1) {
229-
if (BC_MUL_UINT_DIGITS == 4) {
230-
bc_write_bcd_representation(prod_uint[i], pend - 3);
231-
pend -= 4;
232-
} else {
233-
bc_write_bcd_representation(prod_uint[i] / 10000, pend - 7);
234-
bc_write_bcd_representation(prod_uint[i] % 10000, pend - 3);
235-
pend -= 8;
236-
}
285+
#if BC_MUL_UINT_DIGITS == 4
286+
bc_write_bcd_representation(prod_uint[i], pend - 3);
287+
pend -= 4;
288+
#else
289+
bc_write_bcd_representation(prod_uint[i] / 10000, pend - 7);
290+
bc_write_bcd_representation(prod_uint[i] % 10000, pend - 3);
291+
pend -= 8;
292+
#endif
237293
i++;
238294
}
239295

0 commit comments

Comments
 (0)