Skip to content

Commit d313c90

Browse files
committed
fix: fix precision issues using davids method
1 parent fb80b83 commit d313c90

File tree

2 files changed

+68
-34
lines changed

2 files changed

+68
-34
lines changed

src/input/datetime.rs

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -113,21 +113,38 @@ impl<'a> EitherTimedelta<'a> {
113113
pub fn total_seconds(&self) -> PyResult<f64> {
114114
match self {
115115
Self::Raw(timedelta) => {
116-
let days: f64 = f64::from(timedelta.day);
117-
let seconds: f64 = f64::from(timedelta.second);
118-
let microseconds: f64 = f64::from(timedelta.microsecond);
119-
let total_seconds: f64 = if !timedelta.positive {
120-
-1.0 * (86400.0 * days + seconds + microseconds / 1_000_000.0)
116+
let mut days: i64 = i64::from(timedelta.day);
117+
let mut seconds: i64 = i64::from(timedelta.second);
118+
let mut microseconds = i64::from(timedelta.microsecond);
119+
if !timedelta.positive {
120+
days = -days;
121+
seconds = -seconds;
122+
microseconds = -microseconds;
123+
}
124+
125+
let days_seconds = (86_400 * days) + seconds;
126+
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
127+
let total_microseconds = days_seconds_as_micros + microseconds;
128+
Ok(total_microseconds as f64 / 1_000_000.0)
121129
} else {
122-
86400.0 * days + seconds + microseconds / 1_000_000.0
123-
};
124-
Ok(total_seconds)
130+
// Fall back to floating-point operations if the multiplication overflows
131+
let total_seconds = days_seconds as f64 + microseconds as f64 / 1_000_000.0;
132+
Ok(total_seconds)
133+
}
125134
}
126135
Self::PyExact(py_timedelta) => {
127-
let days: f64 = f64::from(py_timedelta.get_days()); // -999999999 to 999999999
128-
let seconds: f64 = f64::from(py_timedelta.get_seconds()); // 0 through 86399
129-
let microseconds: f64 = f64::from(py_timedelta.get_microseconds()); // 0 through 999999
130-
Ok(86400.0 * days + seconds + microseconds / 1_000_000.0)
136+
let days: i64 = py_timedelta.get_days().into(); // -999999999 to 999999999
137+
let seconds: i64 = py_timedelta.get_seconds().into(); // 0 through 86399
138+
let microseconds = py_timedelta.get_microseconds(); // 0 through 999999
139+
let days_seconds = (86_400 * days) + seconds;
140+
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
141+
let total_microseconds = days_seconds_as_micros + i64::from(microseconds);
142+
Ok(total_microseconds as f64 / 1_000_000.0)
143+
} else {
144+
// Fall back to floating-point operations if the multiplication overflows
145+
let total_seconds = days_seconds as f64 + f64::from(microseconds) / 1_000_000.0;
146+
Ok(total_seconds)
147+
}
131148
}
132149
Self::PySubclass(py_timedelta) => py_timedelta
133150
.call_method0(intern!(py_timedelta.py(), "total_seconds"))?
@@ -138,28 +155,45 @@ impl<'a> EitherTimedelta<'a> {
138155
pub fn total_milliseconds(&self) -> PyResult<f64> {
139156
match self {
140157
Self::Raw(timedelta) => {
141-
let days: f64 = f64::from(timedelta.day);
142-
let seconds: f64 = f64::from(timedelta.second);
143-
let microseconds: f64 = f64::from(timedelta.microsecond);
144-
let total_seconds: f64 = if !timedelta.positive {
145-
-1.0 * (86_400_000.0 * days + seconds * 1_000.0 + microseconds / 1_000.0)
158+
let mut days: i64 = i64::from(timedelta.day);
159+
let mut seconds: i64 = i64::from(timedelta.second);
160+
let mut microseconds = i64::from(timedelta.microsecond);
161+
if !timedelta.positive {
162+
days = -days;
163+
seconds = -seconds;
164+
microseconds = -microseconds;
165+
}
166+
167+
let days_seconds = (86_400 * days) + seconds;
168+
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
169+
let total_microseconds = days_seconds_as_micros + microseconds;
170+
Ok(total_microseconds as f64 / 1_000.0)
146171
} else {
147-
86_400_000.0 * days + seconds * 1_000.0 + microseconds / 1_000.0
148-
};
149-
Ok(total_seconds)
172+
// Fall back to floating-point operations if the multiplication overflows
173+
let total_seconds = days_seconds as f64 + microseconds as f64 / 1_000.0;
174+
Ok(total_seconds)
175+
}
150176
}
151177
Self::PyExact(py_timedelta) => {
152-
let days: f64 = f64::from(py_timedelta.get_days()); // -999999999 to 999999999
153-
let seconds: f64 = f64::from(py_timedelta.get_seconds()); // 0 through 86399
154-
let microseconds: f64 = f64::from(py_timedelta.get_microseconds()); // 0 through 999999
155-
Ok(86_400_000.0 * days + seconds * 1_000.0 + microseconds / 1_000.0)
178+
let days: i64 = py_timedelta.get_days().into(); // -999999999 to 999999999
179+
let seconds: i64 = py_timedelta.get_seconds().into(); // 0 through 86399
180+
let microseconds = py_timedelta.get_microseconds(); // 0 through 999999
181+
let days_seconds = (86_400 * days) + seconds;
182+
if let Some(days_seconds_as_micros) = days_seconds.checked_mul(1_000_000) {
183+
let total_microseconds = days_seconds_as_micros + i64::from(microseconds);
184+
Ok(total_microseconds as f64 / 1_000.0)
185+
} else {
186+
// Fall back to floating-point operations if the multiplication overflows
187+
let total_milliseconds = days_seconds as f64 * 1_000.0 + f64::from(microseconds) / 1_000.0;
188+
Ok(total_milliseconds)
189+
}
156190
}
157191
Self::PySubclass(py_timedelta) => {
158-
let total_seconds = py_timedelta
192+
let total_seconds: f64 = py_timedelta
159193
.call_method0(intern!(py_timedelta.py(), "total_seconds"))?
160194
.extract()?;
161195
Ok(total_seconds / 1000.0)
162-
},
196+
}
163197
}
164198
}
165199
}

tests/serializers/test_any.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,10 @@ def test_any_with_timedelta_serializer():
194194
(timedelta(microseconds=1), 0.001, b'0.001', {'0.001': 'foo'}, b'{"0.001":"foo"}', 'milliseconds_float'),
195195
(
196196
timedelta(microseconds=-1),
197-
-0.0009999999999763531,
198-
b'-0.0009999999999763531',
199-
{'-0.0009999999999763531': 'foo'},
200-
b'{"-0.0009999999999763531":"foo"}',
197+
-0.001,
198+
b'-0.001',
199+
{'-0.001': 'foo'},
200+
b'{"-0.001":"foo"}',
201201
'milliseconds_float',
202202
),
203203
(
@@ -263,10 +263,10 @@ def test_any_with_timedelta_serializer():
263263
(timedelta(microseconds=1), 1e-6, b'1e-6', {'0.000001': 'foo'}, b'{"0.000001":"foo"}', 'seconds_float'),
264264
(
265265
timedelta(microseconds=-1),
266-
-1.0000000000287557e-6,
267-
b'-1.0000000000287557e-6',
268-
{'-0.0000010000000000287557': 'foo'},
269-
b'{"-0.0000010000000000287557":"foo"}',
266+
-1e-6,
267+
b'-1e-6',
268+
{'-0.000001': 'foo'},
269+
b'{"-0.000001":"foo"}',
270270
'seconds_float',
271271
),
272272
(timedelta(days=1), 86400.0, b'86400.0', {'86400': 'foo'}, b'{"86400":"foo"}', 'seconds_float'),

0 commit comments

Comments
 (0)