Skip to content

Conversation

@skirpichev
Copy link
Member

@skirpichevskirpichev commented Feb 4, 2025

According to the documentation: "If rdata is non-NULL, it MUST be allocated by one of libmpdec’s allocation functions and rlen MUST be correct. If necessary, the function will resize rdata. Resizing is slow and should not occur if rlen has been obtained by a call to mpd_sizeinbase."

So, possible resizing in mpd_qexport_u32/16() is for guarding against broken log10() implementations (log10 is used in the mpd_sizeinbase()).


Temporary memory allocation slowdown conversion for small integers (~2 digits):

Benchmarkrefpatch
int(Decimal(1<<7))499 ns495 ns: 1.01x faster
int(Decimal(1<<300))2.16 us2.06 us: 1.05x faster
Geometric mean(ref)1.02x faster

Benchmark hidden because not significant (2): int(Decimal(1<<38)), int(Decimal(1<<3000))

Details
# bench_Decimal-to-int.pyimportpyperffromdecimalimportDecimalvalues= ['1<<7', '1<<38', '1<<300', '1<<3000'] runner=pyperf.Runner() forvinvalues: d=Decimal(eval(v)) bn='int(Decimal('+v+'))'runner.bench_func(bn, int, d)

According to the documentation: "If rdata is non-NULL, it MUST be allocated by one of libmpdec’s allocation functions and rlen MUST be correct. If necessary, the function will resize rdata. Resizing is slow and should not occur if rlen has been obtained by a call to mpd_sizeinbase." So, possible resizing in mpd_qexport_u32/16() is for guarding against broken log10() implementations (log10 is used in the mpd_sizeinbase()).
@skirpichev
Copy link
MemberAuthor

skirpichev commented Feb 4, 2025

Edit: For a context, Serhiy's concerns about using mpd_sizeinbase() from old pr:

In our case (base is a power of 2) we can avoid using mpd_sizeinbase() and implement more accurate version. Here is the mod_sizeinbase code:

size_t
mpd_sizeinbase(constmpd_t*a, uint32_tbase)
{
doublex;
size_tdigits;
doubleupper_bound;
assert(mpd_isinteger(a));
assert(base >= 2);
if (mpd_iszero(a)){
return1;
}
digits=a->digits+a->exp;
#ifdefCONFIG_64
/* ceil(2711437152599294 / log10(2)) + 4 == 2**53 */
if (digits>2711437152599294ULL){
returnSIZE_MAX;
}
upper_bound= (double)((1ULL<<53)-1);
#else
upper_bound= (double)(SIZE_MAX-1);
#endif
x= (double)digits / log10(base);
return (x>upper_bound) ? SIZE_MAX : (size_t)x+1;
}

Essentially, it does ndigits * log2(10)/shift. This should be also a correct bound:

(size_t)(3.321928094887363*((ndigits + shift - 1)/shift)) 

For shift=30 and ndigits ~ 1<<53 (upper_bound for typical case) - it will overestimate size in just 1 digit.

@skirpichevskirpichev marked this pull request as ready for review February 4, 2025 08:33
Copy link
Contributor

@erlend-aaslanderlend-aasland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the previous PR and discussions, this looks correct to me. I'd like Serhiy's opinion on it, though, since he expressed concerns earlier.

@skirpichev
Copy link
MemberAuthor

@erlend-aasland, It seems that, that Serhiy is still against this change, see the issue thread.

So, I'm going to close this in few days, unless someone else can argue further.

@skirpichev
Copy link
MemberAuthor

Thanks for review to all.

@skirpichevskirpichev deleted the dec_as_long-nocopy/129275 branch May 24, 2025 01:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@skirpichev@erlend-aasland