@@ -143,6 +143,8 @@ static PyObject *default_context_template = NULL;
143
143
static PyObject * basic_context_template = NULL ;
144
144
static PyObject * extended_context_template = NULL ;
145
145
146
+ /* Invariant: NULL or pointer to _pydecimal.Decimal */
147
+ static PyObject * PyDecimal = NULL ;
146
148
147
149
/* Error codes for functions that return signals or conditions */
148
150
#define DEC_INVALID_SIGNALS (MPD_Max_status+1U)
@@ -3219,56 +3221,6 @@ dotsep_as_utf8(const char *s)
3219
3221
return utf8 ;
3220
3222
}
3221
3223
3222
- /* copy of libmpdec _mpd_round() */
3223
- static void
3224
- _mpd_round (mpd_t * result , const mpd_t * a , mpd_ssize_t prec ,
3225
- const mpd_context_t * ctx , uint32_t * status )
3226
- {
3227
- mpd_ssize_t exp = a -> exp + a -> digits - prec ;
3228
-
3229
- if (prec <= 0 ) {
3230
- mpd_seterror (result , MPD_Invalid_operation , status );
3231
- return ;
3232
- }
3233
- if (mpd_isspecial (a ) || mpd_iszero (a )) {
3234
- mpd_qcopy (result , a , status );
3235
- return ;
3236
- }
3237
-
3238
- mpd_qrescale_fmt (result , a , exp , ctx , status );
3239
- if (result -> digits > prec ) {
3240
- mpd_qrescale_fmt (result , result , exp + 1 , ctx , status );
3241
- }
3242
- }
3243
-
3244
- /* Locate negative zero "z" option within a UTF-8 format spec string.
3245
- * Returns pointer to "z", else NULL.
3246
- * The portion of the spec we're working with is [[fill]align][sign][z] */
3247
- static const char *
3248
- format_spec_z_search (char const * fmt , Py_ssize_t size ) {
3249
- char const * pos = fmt ;
3250
- char const * fmt_end = fmt + size ;
3251
- /* skip over [[fill]align] (fill may be multi-byte character) */
3252
- pos += 1 ;
3253
- while (pos < fmt_end && * pos & 0x80 ) {
3254
- pos += 1 ;
3255
- }
3256
- if (pos < fmt_end && strchr ("<>=^" , * pos ) != NULL ) {
3257
- pos += 1 ;
3258
- } else {
3259
- /* fill not present-- skip over [align] */
3260
- pos = fmt ;
3261
- if (pos < fmt_end && strchr ("<>=^" , * pos ) != NULL ) {
3262
- pos += 1 ;
3263
- }
3264
- }
3265
- /* skip over [sign] */
3266
- if (pos < fmt_end && strchr ("+- " , * pos ) != NULL ) {
3267
- pos += 1 ;
3268
- }
3269
- return pos < fmt_end && * pos == 'z' ? pos : NULL ;
3270
- }
3271
-
3272
3224
static int
3273
3225
dict_get_item_string (PyObject * dict , const char * key , PyObject * * valueobj , const char * * valuestr )
3274
3226
{
@@ -3294,6 +3246,48 @@ dict_get_item_string(PyObject *dict, const char *key, PyObject **valueobj, const
3294
3246
return 0 ;
3295
3247
}
3296
3248
3249
+ /*
3250
+ * Fallback _pydecimal formatting for new format specifiers that mpdecimal does
3251
+ * not yet support. As documented, libmpdec follows the PEP-3101 format language:
3252
+ * https://www.bytereef.org/mpdecimal/doc/libmpdec/assign-convert.html#to-string
3253
+ */
3254
+ static PyObject *
3255
+ pydec_format (PyObject * dec , PyObject * context , PyObject * fmt )
3256
+ {
3257
+ PyObject * result ;
3258
+ PyObject * pydec ;
3259
+ PyObject * u ;
3260
+
3261
+ if (PyDecimal == NULL ) {
3262
+ PyDecimal = _PyImport_GetModuleAttrString ("_pydecimal" , "Decimal" );
3263
+ if (PyDecimal == NULL ) {
3264
+ return NULL ;
3265
+ }
3266
+ }
3267
+
3268
+ u = dec_str (dec );
3269
+ if (u == NULL ) {
3270
+ return NULL ;
3271
+ }
3272
+
3273
+ pydec = PyObject_CallOneArg (PyDecimal , u );
3274
+ Py_DECREF (u );
3275
+ if (pydec == NULL ) {
3276
+ return NULL ;
3277
+ }
3278
+
3279
+ result = PyObject_CallMethod (pydec , "__format__" , "(OO)" , fmt , context );
3280
+ Py_DECREF (pydec );
3281
+
3282
+ if (result == NULL && PyErr_ExceptionMatches (PyExc_ValueError )) {
3283
+ /* Do not confuse users with the _pydecimal exception */
3284
+ PyErr_Clear ();
3285
+ PyErr_SetString (PyExc_ValueError , "invalid format string" );
3286
+ }
3287
+
3288
+ return result ;
3289
+ }
3290
+
3297
3291
/* Formatted representation of a PyDecObject. */
3298
3292
static PyObject *
3299
3293
dec_format (PyObject * dec , PyObject * args )
@@ -3306,16 +3300,11 @@ dec_format(PyObject *dec, PyObject *args)
3306
3300
PyObject * fmtarg ;
3307
3301
PyObject * context ;
3308
3302
mpd_spec_t spec ;
3309
- char const * fmt ;
3310
- char * fmt_copy = NULL ;
3303
+ char * fmt ;
3311
3304
char * decstring = NULL ;
3312
3305
uint32_t status = 0 ;
3313
3306
int replace_fillchar = 0 ;
3314
- int no_neg_0 = 0 ;
3315
3307
Py_ssize_t size ;
3316
- mpd_t * mpd = MPD (dec );
3317
- mpd_uint_t dt [MPD_MINALLOC_MAX ];
3318
- mpd_t tmp = {MPD_STATIC |MPD_STATIC_DATA ,0 ,0 ,0 ,MPD_MINALLOC_MAX ,dt };
3319
3308
3320
3309
3321
3310
CURRENT_CONTEXT (context );
@@ -3324,39 +3313,20 @@ dec_format(PyObject *dec, PyObject *args)
3324
3313
}
3325
3314
3326
3315
if (PyUnicode_Check (fmtarg )) {
3327
- fmt = PyUnicode_AsUTF8AndSize (fmtarg , & size );
3316
+ fmt = ( char * ) PyUnicode_AsUTF8AndSize (fmtarg , & size );
3328
3317
if (fmt == NULL ) {
3329
3318
return NULL ;
3330
3319
}
3331
- /* NOTE: If https://github.com/python/cpython/pull/29438 lands, the
3332
- * format string manipulation below can be eliminated by enhancing
3333
- * the forked mpd_parse_fmt_str(). */
3320
+
3334
3321
if (size > 0 && fmt [0 ] == '\0' ) {
3335
3322
/* NUL fill character: must be replaced with a valid UTF-8 char
3336
3323
before calling mpd_parse_fmt_str(). */
3337
3324
replace_fillchar = 1 ;
3338
- fmt = fmt_copy = dec_strdup (fmt , size );
3339
- if (fmt_copy == NULL ) {
3325
+ fmt = dec_strdup (fmt , size );
3326
+ if (fmt == NULL ) {
3340
3327
return NULL ;
3341
3328
}
3342
- fmt_copy [0 ] = '_' ;
3343
- }
3344
- /* Strip 'z' option, which isn't understood by mpd_parse_fmt_str().
3345
- * NOTE: fmt is always null terminated by PyUnicode_AsUTF8AndSize() */
3346
- char const * z_position = format_spec_z_search (fmt , size );
3347
- if (z_position != NULL ) {
3348
- no_neg_0 = 1 ;
3349
- size_t z_index = z_position - fmt ;
3350
- if (fmt_copy == NULL ) {
3351
- fmt = fmt_copy = dec_strdup (fmt , size );
3352
- if (fmt_copy == NULL ) {
3353
- return NULL ;
3354
- }
3355
- }
3356
- /* Shift characters (including null terminator) left,
3357
- overwriting the 'z' option. */
3358
- memmove (fmt_copy + z_index , fmt_copy + z_index + 1 , size - z_index );
3359
- size -= 1 ;
3329
+ fmt [0 ] = '_' ;
3360
3330
}
3361
3331
}
3362
3332
else {
@@ -3366,10 +3336,13 @@ dec_format(PyObject *dec, PyObject *args)
3366
3336
}
3367
3337
3368
3338
if (!mpd_parse_fmt_str (& spec , fmt , CtxCaps (context ))) {
3369
- PyErr_SetString (PyExc_ValueError ,
3370
- "invalid format string" );
3371
- goto finish ;
3339
+ if (replace_fillchar ) {
3340
+ PyMem_Free (fmt );
3341
+ }
3342
+
3343
+ return pydec_format (dec , context , fmtarg );
3372
3344
}
3345
+
3373
3346
if (replace_fillchar ) {
3374
3347
/* In order to avoid clobbering parts of UTF-8 thousands separators or
3375
3348
decimal points when the substitution is reversed later, the actual
@@ -3422,45 +3395,8 @@ dec_format(PyObject *dec, PyObject *args)
3422
3395
}
3423
3396
}
3424
3397
3425
- if (no_neg_0 && mpd_isnegative (mpd ) && !mpd_isspecial (mpd )) {
3426
- /* Round into a temporary (carefully mirroring the rounding
3427
- of mpd_qformat_spec()), and check if the result is negative zero.
3428
- If so, clear the sign and format the resulting positive zero. */
3429
- mpd_ssize_t prec ;
3430
- mpd_qcopy (& tmp , mpd , & status );
3431
- if (spec .prec >= 0 ) {
3432
- switch (spec .type ) {
3433
- case 'f' :
3434
- mpd_qrescale (& tmp , & tmp , - spec .prec , CTX (context ), & status );
3435
- break ;
3436
- case '%' :
3437
- tmp .exp += 2 ;
3438
- mpd_qrescale (& tmp , & tmp , - spec .prec , CTX (context ), & status );
3439
- break ;
3440
- case 'g' :
3441
- prec = (spec .prec == 0 ) ? 1 : spec .prec ;
3442
- if (tmp .digits > prec ) {
3443
- _mpd_round (& tmp , & tmp , prec , CTX (context ), & status );
3444
- }
3445
- break ;
3446
- case 'e' :
3447
- if (!mpd_iszero (& tmp )) {
3448
- _mpd_round (& tmp , & tmp , spec .prec + 1 , CTX (context ), & status );
3449
- }
3450
- break ;
3451
- }
3452
- }
3453
- if (status & MPD_Errors ) {
3454
- PyErr_SetString (PyExc_ValueError , "unexpected error when rounding" );
3455
- goto finish ;
3456
- }
3457
- if (mpd_iszero (& tmp )) {
3458
- mpd_set_positive (& tmp );
3459
- mpd = & tmp ;
3460
- }
3461
- }
3462
3398
3463
- decstring = mpd_qformat_spec (mpd , & spec , CTX (context ), & status );
3399
+ decstring = mpd_qformat_spec (MPD ( dec ) , & spec , CTX (context ), & status );
3464
3400
if (decstring == NULL ) {
3465
3401
if (status & MPD_Malloc_error ) {
3466
3402
PyErr_NoMemory ();
@@ -3483,7 +3419,7 @@ dec_format(PyObject *dec, PyObject *args)
3483
3419
Py_XDECREF (grouping );
3484
3420
Py_XDECREF (sep );
3485
3421
Py_XDECREF (dot );
3486
- if (fmt_copy ) PyMem_Free (fmt_copy );
3422
+ if (replace_fillchar ) PyMem_Free (fmt );
3487
3423
if (decstring ) mpd_free (decstring );
3488
3424
return result ;
3489
3425
}
@@ -5893,6 +5829,9 @@ PyInit__decimal(void)
5893
5829
/* Create the module */
5894
5830
ASSIGN_PTR (m , PyModule_Create (& _decimal_module ));
5895
5831
5832
+ /* For format specifiers not yet supported by libmpdec */
5833
+ PyDecimal = NULL ;
5834
+
5896
5835
/* Add types to the module */
5897
5836
CHECK_INT (PyModule_AddObjectRef (m , "Decimal" , (PyObject * )& PyDec_Type ));
5898
5837
CHECK_INT (PyModule_AddObjectRef (m , "Context" , (PyObject * )& PyDecContext_Type ));
0 commit comments