@@ -137,6 +137,9 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator,
137
137
/* volatile avoids optimization changing how numbers are rounded */
138
138
volatile double floatpart ;
139
139
140
+ /* For correctly rounding ROUND_HALF_EVEN, the denominator must be even */
141
+ assert (idenominator % 2 == 0 );
142
+
140
143
floatpart = modf (d , & intpart );
141
144
142
145
floatpart *= denominator ;
@@ -167,57 +170,117 @@ _PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
167
170
{
168
171
assert (denominator >= 1 );
169
172
170
- if (PyFloat_Check (obj )) {
171
- double d = PyFloat_AsDouble (obj );
173
+ /* Try the methods __index__, __float__, __int__ in this order
174
+ * to ensure best possible precision. See the discussion at
175
+ * https://bugs.python.org/issue35707 */
176
+
177
+ PyObject * intobj = NULL ;
178
+ if (PyIndex_Check (obj )) {
179
+ intobj = PyNumber_Index (obj );
180
+ if (intobj != NULL ) {
181
+ /* Success with __index__: skip the __float__ check */
182
+ obj = intobj ;
183
+ goto convert_from_int ;
184
+ }
185
+ /* If __index__ raises TypeError, try __float__ */
186
+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
187
+ PyErr_Clear ();
188
+ }
189
+ else {
190
+ return -1 ;
191
+ }
192
+ }
193
+
194
+ /* If we get here, then intobj is NULL */
195
+ double d = PyFloat_AsDouble (obj );
196
+ if (d == -1.0 && PyErr_Occurred ()) {
197
+ /* If __float__ raises TypeError, try __int__ */
198
+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
199
+ PyErr_Clear ();
200
+ }
201
+ else {
202
+ return -1 ;
203
+ }
204
+ }
205
+ else {
206
+ /* Success with __float__ */
172
207
if (Py_IS_NAN (d )) {
173
- * numerator = 0 ;
174
208
PyErr_SetString (PyExc_ValueError , "Invalid value NaN (not a number)" );
175
209
return -1 ;
176
210
}
177
211
return _PyTime_DoubleToDenominator (d , sec , numerator ,
178
212
denominator , round );
179
213
}
180
- else {
181
- * sec = _PyLong_AsTime_t ( obj );
182
- * numerator = 0 ;
183
- if ( * sec == ( time_t ) - 1 && PyErr_Occurred ()) {
184
- return -1 ;
185
- }
186
- return 0 ;
214
+
215
+ convert_from_int :
216
+ * sec = _PyLong_AsTime_t ( obj ) ;
217
+ * numerator = 0 ;
218
+ Py_XDECREF ( intobj ) ;
219
+ if ( * sec == ( time_t ) - 1 && PyErr_Occurred ()) {
220
+ return -1 ;
187
221
}
222
+ return 0 ;
188
223
}
189
224
225
+
190
226
int
191
227
_PyTime_ObjectToTime_t (PyObject * obj , time_t * sec , _PyTime_round_t round )
192
228
{
193
- if (PyFloat_Check (obj )) {
194
- double intpart ;
195
- /* volatile avoids optimization changing how numbers are rounded */
196
- volatile double d ;
229
+ /* Try the methods __index__, __float__, __int__ in this order
230
+ * to ensure best possible precision. See the discussion at
231
+ * https://bugs.python.org/issue35707 */
232
+
233
+ PyObject * intobj = NULL ;
234
+ if (PyIndex_Check (obj )) {
235
+ intobj = PyNumber_Index (obj );
236
+ if (intobj != NULL ) {
237
+ /* Success with __index__: skip the __float__ check */
238
+ obj = intobj ;
239
+ goto convert_from_int ;
240
+ }
241
+ /* If __index__ raises TypeError, try __float__ */
242
+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
243
+ PyErr_Clear ();
244
+ }
245
+ else {
246
+ return -1 ;
247
+ }
248
+ }
197
249
198
- d = PyFloat_AsDouble (obj );
250
+ /* If we get here, then intobj is NULL */
251
+ double d = PyFloat_AsDouble (obj );
252
+ if (d == -1.0 && PyErr_Occurred ()) {
253
+ /* If __float__ raises TypeError, try __int__ */
254
+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
255
+ PyErr_Clear ();
256
+ }
257
+ else {
258
+ return -1 ;
259
+ }
260
+ }
261
+ else {
262
+ /* Success with __float__ */
199
263
if (Py_IS_NAN (d )) {
200
264
PyErr_SetString (PyExc_ValueError , "Invalid value NaN (not a number)" );
201
265
return -1 ;
202
266
}
203
267
204
268
d = _PyTime_Round (d , round );
205
- (void )modf (d , & intpart );
206
-
207
- if (!_Py_InIntegralTypeRange (time_t , intpart )) {
269
+ if (!_Py_InIntegralTypeRange (time_t , d )) {
208
270
error_time_t_overflow ();
209
271
return -1 ;
210
272
}
211
- * sec = (time_t )intpart ;
273
+ * sec = (time_t )d ;
212
274
return 0 ;
213
275
}
214
- else {
215
- * sec = _PyLong_AsTime_t ( obj );
216
- if ( * sec == ( time_t ) - 1 && PyErr_Occurred ()) {
217
- return -1 ;
218
- }
219
- return 0 ;
276
+
277
+ convert_from_int :
278
+ * sec = _PyLong_AsTime_t ( obj );
279
+ Py_XDECREF ( intobj ) ;
280
+ if ( * sec == ( time_t ) - 1 && PyErr_Occurred ()) {
281
+ return -1 ;
220
282
}
283
+ return 0 ;
221
284
}
222
285
223
286
int
@@ -401,34 +464,65 @@ static int
401
464
_PyTime_FromObject (_PyTime_t * t , PyObject * obj , _PyTime_round_t round ,
402
465
long unit_to_ns )
403
466
{
404
- if (PyFloat_Check (obj )) {
405
- double d ;
406
- d = PyFloat_AsDouble (obj );
467
+ /* Try the methods __index__, __float__, __int__ in this order
468
+ * to ensure best possible precision. See the discussion at
469
+ * https://bugs.python.org/issue35707 */
470
+
471
+ PyObject * intobj = NULL ;
472
+ if (PyIndex_Check (obj )) {
473
+ intobj = PyNumber_Index (obj );
474
+ if (intobj != NULL ) {
475
+ /* Success with __index__: skip the __float__ check */
476
+ obj = intobj ;
477
+ goto convert_from_int ;
478
+ }
479
+ /* If __index__ raises TypeError, try __float__ */
480
+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
481
+ PyErr_Clear ();
482
+ }
483
+ else {
484
+ return -1 ;
485
+ }
486
+ }
487
+
488
+ /* If we get here, then intobj is NULL */
489
+ double d = PyFloat_AsDouble (obj );
490
+ if (d == -1.0 && PyErr_Occurred ()) {
491
+ /* If __float__ raises TypeError, try __int__ */
492
+ if (PyErr_ExceptionMatches (PyExc_TypeError )) {
493
+ PyErr_Clear ();
494
+ }
495
+ else {
496
+ return -1 ;
497
+ }
498
+ }
499
+ else {
500
+ /* Success with __float__ */
407
501
if (Py_IS_NAN (d )) {
408
502
PyErr_SetString (PyExc_ValueError , "Invalid value NaN (not a number)" );
409
503
return -1 ;
410
504
}
411
505
return _PyTime_FromDouble (t , d , round , unit_to_ns );
412
506
}
413
- else {
414
- long long sec ;
415
- Py_BUILD_ASSERT (sizeof (long long ) <= sizeof (_PyTime_t ));
416
507
417
- sec = PyLong_AsLongLong (obj );
418
- if (sec == -1 && PyErr_Occurred ()) {
419
- if (PyErr_ExceptionMatches (PyExc_OverflowError )) {
420
- _PyTime_overflow ();
421
- }
422
- return -1 ;
423
- }
508
+ convert_from_int :
509
+ Py_BUILD_ASSERT (sizeof (long long ) <= sizeof (_PyTime_t ));
424
510
425
- if (_PyTime_check_mul_overflow (sec , unit_to_ns )) {
511
+ long long sec = PyLong_AsLongLong (obj );
512
+ Py_XDECREF (intobj );
513
+ if (sec == -1 && PyErr_Occurred ()) {
514
+ if (PyErr_ExceptionMatches (PyExc_OverflowError )) {
426
515
_PyTime_overflow ();
427
- return -1 ;
428
516
}
429
- * t = sec * unit_to_ns ;
430
- return 0 ;
517
+ return -1 ;
518
+ }
519
+
520
+ if (_PyTime_check_mul_overflow (sec , unit_to_ns )) {
521
+ _PyTime_overflow ();
522
+ return -1 ;
431
523
}
524
+ * t = sec * unit_to_ns ;
525
+ return 0 ;
432
526
}
433
527
434
528
int
@@ -451,8 +545,8 @@ _PyTime_AsSecondsDouble(_PyTime_t t)
451
545
452
546
if (t % SEC_TO_NS == 0 ) {
453
547
_PyTime_t secs ;
454
- /* Divide using integers to avoid rounding issues on the integer part.
455
- 1e-9 cannot be stored exactly in IEEE 64-bit. */
548
+ /* Divide using integers to avoid rounding issues when
549
+ * converting t to double */
456
550
secs = t / SEC_TO_NS ;
457
551
d = (double )secs ;
458
552
}
0 commit comments