This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit b568f20f325808ffa04679c81e3caf6d7538450a Author: Niklas Haas <[email protected]> AuthorDate: Wed Jun 10 14:58:39 2026 +0200 Commit: Niklas Haas <[email protected]> CommitDate: Thu Jun 25 01:20:15 2026 +0200 swscale/rational64: add 64-bit rational type This is needed by the ops code, to represent intermediate values for 32-bit formats, which can exceed the value range of int32_t (especially for intermediate products). I copied the math almost 1:1 from rational.c, but adapted to use the 128-bit integer wrappers defined by int128.h. I added a generous amount of tests in any case. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libavutil/tests/rational.c | 123 +++++++++++++++++++++++++++++++++++++ libswscale/Makefile | 1 + libswscale/rational64.c | 146 ++++++++++++++++++++++++++++++++++++++++++++ libswscale/rational64.h | 147 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+) diff --git a/libavutil/tests/rational.c b/libavutil/tests/rational.c index 2c9ca947fc..69a8e896f7 100644 --- a/libavutil/tests/rational.c +++ b/libavutil/tests/rational.c @@ -20,12 +20,14 @@ */ #include "libavutil/rational.c" +#include "libswscale/rational64.c" #include "libavutil/integer.h" #include "libavutil/intfloat.h" int main(void) { AVRational a,b,r; + AVRational64 a64,b64,r64; int i,j,k; static const int64_t numlist[] = { INT64_MIN, INT64_MIN+1, INT64_MAX, INT32_MIN, INT32_MAX, 1,0,-1, @@ -53,6 +55,69 @@ int main(void) } } + for (a64.num = -2; a64.num <= 2; a64.num++) { + for (a64.den = -2; a64.den <= 2; a64.den++) { + for (b64.num = -2; b64.num <= 2; b64.num++) { + for (b64.den = -2; b64.den <= 2; b64.den++) { + const double adbl = av_q2d_64(a64); + const double bdbl = av_q2d_64(b64); + const int c = av_cmp_q64(a64,b64); + const int d = adbl == bdbl ? 0 : + adbl > bdbl ? 1 : + adbl < bdbl ? -1 : INT_MIN; + + if (c != d) + av_log(NULL, AV_LOG_ERROR, "%lld/%lld %lld/%lld, %d != %d\n", + (long long) a64.num, (long long) a64.den, + (long long) b64.num, (long long) b64.den, c,d); + + // Check arithmetic result + if (a64.den && b64.den) { + double rdbl; + + r64 = av_add_q64(a64, b64); + rdbl = av_q2d_64(r64); + if (rdbl != adbl + bdbl) { + av_log(NULL, AV_LOG_ERROR, "%f + %f = %f != %f\n", + adbl, bdbl, rdbl, adbl + bdbl); + } + + r64 = av_mul_q64(a64, b64); + rdbl = av_q2d_64(r64); + if (rdbl != adbl * bdbl) { + av_log(NULL, AV_LOG_ERROR, "%f * %f = %f != %f\n", + adbl, bdbl, rdbl, adbl * bdbl); + } + } + + // Check addition round-trip + r64 = av_sub_q64(av_add_q64(a64, b64), b64); + if (b64.den && (r64.num*a64.den != a64.num*r64.den || + !r64.num != !a64.num || + !r64.den != !a64.den)) + { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld != %lld/%lld\n", + (long long) a64.num, (long long) a64.den, + (long long) r64.num, (long long) r64.den); + } + + if (b64.num) { + // Check multiplication round-trip + r64 = av_div_q64(av_mul_q64(a64, b64), b64); + if (b64.den && (r64.num*a64.den != a64.num*r64.den || + !r64.num != !a64.num || + !r64.den != !a64.den)) + { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld != %lld/%lld\n", + (long long) a64.num, (long long) a64.den, + (long long) r64.num, (long long) r64.den); + } + } + } + } + } + } + /* Check overflow behavior and edge cases */ static const AVRational unit_mul_q[][3] = { {{INT_MAX, 2}, { 2, 1}, { INT_MAX, 1}}, @@ -106,6 +171,64 @@ int main(void) } } + static const AVRational64 unit_mul_q64[][3] = { + {{INT64_MAX, 2}, { 2, 1}, { INT64_MAX, 1}}, + {{INT64_MAX, 2}, {-2, 1}, {-INT64_MAX, 1}}, + {{INT64_MAX, 2}, { 0, 1}, {0, 1}}, + {{INT64_MIN, 2}, { 2, 1}, {-INT64_MAX, 1}}, /* not INT64_MIN */ + {{INT64_MIN, 2}, {-2, 1}, { INT64_MAX, 1}}, + {{INT64_MIN, 2}, { 0, 1}, {0, 1}}, + {{INT64_MAX >> 8, 1}, {INT64_MAX >> 8, 1}, {INT64_MAX, 1}}, + {{1, INT64_MAX >> 8}, {1, INT64_MAX >> 8}, {0, 1}}, + {{1, 1}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}}, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(unit_mul_q64); i++) { + for (int c = 0; c < 2; c++) { /* test commutativity */ + AVRational64 a = unit_mul_q64[i][c ? 1 : 0]; + AVRational64 b = unit_mul_q64[i][c ? 0 : 1]; + AVRational64 c = unit_mul_q64[i][2]; + AVRational64 r = av_mul_q64(a, b); + if (r.num != c.num || r.den != c.den) { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld * %lld/%lld = %lld/%lld, expected %lld/%lld\n", + (long long) a.num, (long long) a.den, + (long long) b.num, (long long) b.den, + (long long) r.num, (long long) r.den, + (long long) c.num, (long long) c.den); + } + } + } + + static const AVRational64 unit_add_q64[][3] = { + {{INT64_MAX, 1}, { 2, 2}, { INT64_MAX, 1}}, + {{INT64_MAX, 1}, {-2, 2}, { INT64_MAX - 1, 1}}, + {{INT64_MAX, 1}, { 0, 2}, { INT64_MAX, 1}}, + {{INT64_MIN, 1}, { 2, 2}, {-INT64_MAX, 1}}, + {{INT64_MIN, 1}, {-2, 2}, {-INT64_MAX, 1}}, + {{INT64_MIN, 1}, { 0, 2}, {-INT64_MAX, 1}}, + {{INT64_MAX - 10, 1}, {20, 1}, { INT64_MAX, 1}}, + {{2, INT64_MAX}, {2, INT64_MAX}, {4, INT64_MAX}}, + {{1, 1}, {0, 0}, {0, 0}}, + {{0, 1}, {0, 0}, {0, 0}}, + }; + + for (i = 0; i < FF_ARRAY_ELEMS(unit_add_q64); i++) { + for (int c = 0; c < 2; c++) { /* test commutativity */ + AVRational64 a = unit_add_q64[i][c ? 1 : 0]; + AVRational64 b = unit_add_q64[i][c ? 0 : 1]; + AVRational64 c = unit_add_q64[i][2]; + AVRational64 r = av_add_q64(a, b); + if (r.num != c.num || r.den != c.den) { + av_log(NULL, AV_LOG_ERROR, "%lld/%lld + %lld/%lld = %lld/%lld, expected %lld/%lld\n", + (long long) a.num, (long long) a.den, + (long long) b.num, (long long) b.den, + (long long) r.num, (long long) r.den, + (long long) c.num, (long long) c.den); + } + } + } + for (i = 0; i < FF_ARRAY_ELEMS(numlist); i++) { int64_t a = numlist[i]; diff --git a/libswscale/Makefile b/libswscale/Makefile index b8dca3c4e9..7ab3f66785 100644 --- a/libswscale/Makefile +++ b/libswscale/Makefile @@ -34,6 +34,7 @@ OBJS-$(CONFIG_UNSTABLE) += \ ops_dispatch.o \ ops_memcpy.o \ ops_optimizer.o \ + rational64.o \ uops.o \ uops_backend.o \ diff --git a/libswscale/rational64.c b/libswscale/rational64.c new file mode 100644 index 0000000000..998e46c572 --- /dev/null +++ b/libswscale/rational64.c @@ -0,0 +1,146 @@ +/* + * 64-bit rational numbers + * Copyright (c) 2025 Niklas Haas + * Copyright (c) 2003 Michael Niedermayer <[email protected]> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * 64-bit rational numbers + * @author Niklas Haas + */ + +#include <limits.h> + +#include "libavutil/int128.h" +#include "rational64.h" + +static av_int128 gcd128(av_int128 a, av_int128 b) +{ + while (av_test128(b)) { + av_int128 tmp = b; + b = av_mod128(a, b); + a = tmp; + } + return a; +} + +static AVRational64 reduce64(av_int128 num, av_int128 den) +{ + const av_int128 zero = av_to128i(0); + const av_int128 max = av_to128i(INT64_MAX); + const int num_sign = av_cmp128(num, zero) < 0; + const int den_sign = av_cmp128(den, zero) < 0; + if (num_sign) + num = av_sub128(zero, num); + if (den_sign) + den = av_sub128(zero, den); + + const av_int128 gcd = gcd128(num, den); + if (av_test128(gcd)) { + num = av_div128(num, gcd); + den = av_div128(den, gcd); + } + + av_uint128 a0n = av_to128u(0), a0d = av_to128u(1); + av_uint128 a1n = av_to128u(1), a1d = av_to128u(0); + if (av_cmp128(num, max) <= 0 && av_cmp128(den, max) <= 0) { + a1n = num; + a1d = den; + goto done; + } + + while (av_test128(den)) { + av_int128 x = av_div128(num, den); + av_int128 next_den = av_sub128(num, av_mul128(den, x)); + av_uint128 a2n = av_add128(av_mul128(x, a1n), a0n); + av_uint128 a2d = av_add128(av_mul128(x, a1d), a0d); + + if (av_cmp128(a2n, max) > 0 || av_cmp128(a2d, max) > 0) { + if (av_test128(a1n)) + x = av_div128(av_sub128(max, a0n), a1n); + if (av_test128(a1d)) { + av_uint128 tmp = av_div128(av_sub128(max, a0d), a1d); + x = av_min128(x, tmp); + } + + av_uint128 x1d = av_mul128(x, a1d); + av_uint128 a = av_mul128(den, av_add128(av_add128(x1d, x1d), a0d)); + av_uint128 b = av_mul128(num, a1d); + if (av_cmp128(a, b) > 0) { + a1n = av_add128(av_mul128(x, a1n), a0n); + a1d = av_add128(x1d, a0d); + } + break; + } + + a0n = a1n; + a0d = a1d; + a1n = a2n; + a1d = a2d; + num = den; + den = next_den; + } + +done:; + AVRational64 res = { av_from128i(a1n), av_from128i(a1d) }; + if (num_sign ^ den_sign) + res.num = -res.num; + return res; +} + +int av_cmp_q64(AVRational64 a, AVRational64 b) +{ + const av_int128 p = av_mul128(av_to128i(a.num), av_to128i(b.den)); + const av_int128 q = av_mul128(av_to128i(b.num), av_to128i(a.den)); + const int test = av_cmp128(p, q); + + if (test) + return (a.den < 0) ^ (b.den < 0) ? -test : test; + else if (b.den && a.den) + return 0; + else if (a.num && b.num) + return (a.num >> 63) - (b.num >> 63); + else + return INT_MIN; +} + +AVRational64 av_mul_q64(AVRational64 b, AVRational64 c) +{ + return reduce64(av_mul128(av_to128i(b.num), av_to128i(c.num)), + av_mul128(av_to128i(b.den), av_to128i(c.den))); +} + +AVRational64 av_div_q64(AVRational64 b, AVRational64 c) +{ + return av_mul_q64(b, av_inv_q64(c)); +} + +AVRational64 av_add_q64(AVRational64 b, AVRational64 c) { + return reduce64(av_add128(av_mul128(av_to128i(b.num), av_to128i(c.den)), + av_mul128(av_to128i(c.num), av_to128i(b.den))), + av_mul128(av_to128i(b.den), av_to128i(c.den))); +} + +AVRational64 av_sub_q64(AVRational64 b, AVRational64 c) +{ + return reduce64(av_sub128(av_mul128(av_to128i(b.num), av_to128i(c.den)), + av_mul128(av_to128i(c.num), av_to128i(b.den))), + av_mul128(av_to128i(b.den), av_to128i(c.den))); +} diff --git a/libswscale/rational64.h b/libswscale/rational64.h new file mode 100644 index 0000000000..f399f9acca --- /dev/null +++ b/libswscale/rational64.h @@ -0,0 +1,147 @@ +/* + * 64-bit rational numbers + * Copyright (c) 2025 Niklas Haas + * Copyright (c) 2003 Michael Niedermayer <[email protected]> + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @ingroup lavu_math_rational + * 64-bit extension of AVRational. + * @author Niklas Haas + */ + +#ifndef SWSCALE_RATIONAL64_H +#define SWSCALE_RATIONAL64_H + +#include <stdint.h> +#include <limits.h> + +#include "libavutil/attributes.h" + +/** + * @defgroup AVRational64 + * 64-bit extension of AVRational + * + * Offers a 64-bit extended version of AVRational. This is less efficient (and + * may revolve around emulated 128-bit multiplications internally), but allows + * to represent a much larger range of rational numbers without overflow. + * + * @{ + */ + +/** + * 64-bit Rational number (pair of numerator and denominator). + */ +typedef struct AVRational64 { + int64_t num; ///< Numerator + int64_t den; ///< Denominator +} AVRational64; + +/** + * Create an AVRational64. + * + * Useful for compilers that do not support compound literals. + * + * @note The return value is not reduced. + */ +static inline AVRational64 av_make_q64(int64_t num, int64_t den) +{ + AVRational64 r = { num, den }; + return r; +} + +/** + * Compare two 64-bit rationals. + * + * @param a First rational + * @param b Second rational + * + * @return One of the following values: + * - 0 if `a == b` + * - 1 if `a > b` + * - -1 if `a < b` + * - `INT_MIN` if one of the values is of the form `0 / 0` + */ +int av_cmp_q64(AVRational64 a, AVRational64 b); + +/** + * Convert an AVRational64 to a `double`. + * @param a AVRational64 to convert + * @return `a` in floating-point form + * @see av_d2q() + */ +static inline double av_q2d_64(AVRational64 a){ + return a.num / (double) a.den; +} + +/** + * Multiply two 64-bit rationals. + * @param b First multiplicant + * @param c Second multiplicant + * @return b*c + */ +AVRational64 av_mul_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Divide one 64-bit rational by another. + * @param b Dividend + * @param c Divisor + * @return b/c + */ +AVRational64 av_div_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Add two 64-bit rationals. + * @param b First addend + * @param c Second addend + * @return b+c + */ +AVRational64 av_add_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Subtract one 64-bit rational from another. + * @param b Minuend + * @param c Subtrahend + * @return b-c + */ +AVRational64 av_sub_q64(AVRational64 b, AVRational64 c) av_const; + +/** + * Invert a 64-bit rational. + * @param q value + * @return 1 / q + */ +static av_always_inline AVRational64 av_inv_q64(AVRational64 q) +{ + AVRational64 r = { q.den, q.num }; + return r; +} + +/** + * Return the best rational so that a and b are multiple of it. + * If the resulting denominator is larger than max_den, return def. + */ +AVRational64 av_gcd_q64(AVRational64 a, AVRational64 b, int max_den, AVRational64 def); + +/** + * @} + */ + +#endif /* SWSCALE_RATIONAL64_H */ _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
