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]

Reply via email to