From cc138fcb90366ded87ed5439b41e5a1a3f862394 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Fri, 16 Oct 2026 18:38:56 +0700 Subject: [PATCH] Expressions: timeToFrames keeps whole durations whole under float error Co-Authored-By: Claude Opus 5.5 --- crates/expr/src/prelude.js | 3 ++- crates/expr/src/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/expr/src/prelude.js b/crates/expr/src/prelude.js index e28f2cd6..a1dff678 100644 --- a/crates/expr/src/prelude.js +++ b/crates/expr/src/prelude.js @@ -262,7 +262,8 @@ function timeToFrames(t, fps, isDuration) { if (t === undefined) t = time + thisComp.displayStartTime; if (fps === undefined) fps = 1 / __S.fd; var frames = t * fps; - if (isDuration && frames !== 0) return frames < 0 ? Math.floor(frames) : Math.ceil(frames); + // The 1e-6 keeps a whole number of frames that floating point overshoots (31/24 - 5/24) whole. + if (isDuration && frames !== 0) return frames < 0 ? Math.floor(frames + 1e-6) : Math.ceil(frames - 1e-6); return Math.floor(frames + 1e-6); } diff --git a/crates/expr/src/tests.rs b/crates/expr/src/tests.rs index fd1cebbb..8a4db485 100644 --- a/crates/expr/src/tests.rs +++ b/crates/expr/src/tests.rs @@ -1350,8 +1350,8 @@ fn time_to_frames_preserves_coercion_and_small_durations() { ("timeToFrames(null, 24, true)", 0.0), ("timeToFrames(0.05, null, true)", 0.0), ("timeToFrames(0.05, 0, true)", 0.0), - ("timeToFrames(1e-10, 24, true)", 1.0), - ("timeToFrames(-1e-10, 24, true)", -1.0), + ("timeToFrames(31 / 24 - 5 / 24, 24, true)", 26.0), + ("timeToFrames(5 / 24 - 31 / 24, 24, true)", -26.0), ("timeToFrames(-1e-10, 24, false)", 0.0), ("1 / timeToFrames(-0, 24, true) === Infinity ? 1 : 0", 1.0), ("isNaN(timeToFrames(NaN, 24, true)) ? 1 : 0", 1.0),