2

Given two integers, how can I minimize them so that their product is less than some other value, while maintaining their relative ratio?

That's the formal problem. The practical problem is this: I have width/height pixel resolutions containing random values (anywhere from 1 to 8192 for either dimension). I want to adjust the pairs of values, such that their product doesn't exceed some total number of pixels (ex: 1000000), and I need to ensure that the aspect ratio of the adjusted resolution remains the same (ex: 1.7777).

The naive approach is to just run a loop where I subtract 1 from the width each time, adjusting the height to match the aspect ratio, until their product is under the threshold. Ex:

int wid = 1920;
int hei = 1080;
float aspect = wid / (float)hei;
int maxPixels = 1000000;
while (wid * hei > maxPixels)
{
    wid -= 1;
    hei = wid / aspect;
}

Surely there must be a more analytical approach to this problem though?

Tyson
  • 1,226
  • 1
  • 10
  • 33
  • 2
    This seems like more a mathematical question. – Asteroids With Wings Mar 08 '20 at 20:09
  • Depending on how performant this needs to be, the naive approach might be close - but try "homing in" instead by, say, dividing both values by two and seeing whether you've overshot. Kind of like a binary search. Go up and down as you oscillate around the target. Reduce the step multiplier as you home in. And I think you mean "maximise" ;) – Asteroids With Wings Mar 08 '20 at 20:10
  • https://math.stackexchange.com/questions/2469446/fast-algorithm-for-computing-integer-square-roots-on-machines-that-doesnt-suppo – S.S. Anne Mar 08 '20 at 20:11
  • Also not sure you mean "square"? You mean just.. multiplying them together, right? – Asteroids With Wings Mar 08 '20 at 20:11
  • @AsteroidsWithWings Ah yea sorry I meant product. – Tyson Mar 08 '20 at 20:13
  • I'm surprised my Google fu isn't better on this one. – Asteroids With Wings Mar 08 '20 at 20:15
  • @Asteroids same. It *seems* like something that should have been solved 1000 times on this site, but only if I squint. Normally you only care about reducing one dimension to below a threshold, and adjust the other one accordingly. Having an area budget is strange. – JohnFilleau Mar 08 '20 at 20:20
  • Does the aspect ratio need to be EXACTLY maintained? (something with width and height as prime numbers, for example, couldn't be scaled at all if you need to maintain the exact ratio) – JohnFilleau Mar 08 '20 at 20:22
  • Good point, some inputs don't have exact solutions. But that goes away if you permit sub-pixel resolution then some subsequent rounding... – Asteroids With Wings Mar 08 '20 at 20:22
  • @John Doesn't have to be the exact ratio, rounding down to the nearest pixel is fine. The reason for the constraint is an MP4 encoding algorithm that has a max pixel budget, but can take any aspect ratios within the limit. Not my design, just a constraint imposed on me :) – Tyson Mar 08 '20 at 20:23
  • Is this real-time or just one and done? Although this is an interesting question it does seem like something that in reality I'd solve quite naively if it was just at the beginning of an encode. – Asteroids With Wings Mar 08 '20 at 20:24
  • It's not realtime, and the naive approach isn't out of the question...I just felt dumb not being able to come up with something more clever, and googling the issue didn't give me anything relevant....so I figured I'd ask here. – Tyson Mar 08 '20 at 20:25
  • 1
    Understood & fair enough. Good to know what your expectations/requirements are. Will watch this one. – Asteroids With Wings Mar 08 '20 at 20:25
  • Can we reduce any of the dimensions to 0? Or is 1 the minimum acceptable dimension? – JohnFilleau Mar 08 '20 at 20:29
  • Reducing to 0 is fine. – Tyson Mar 08 '20 at 20:38

2 Answers2

2

Edit: Misread the original question.

Another way to word your question is with a W and H what is the largest a and b such that a/b = W/H and a*b < C where C is your limit on .

To do so, find the D = gcd(W,H) or greatest common divisor of W and H. Greatest common denominator is commonly found using the Euclidean Algorithm.

Set x = W/D and y = H/D, this is minimal solution with the same ratio.

To produce the maxima under C, start with the inequality of F*x*F*y <= C where F will be our scale factor for x and y

Algebra:

F^2 <= C/(x*y)

F <= sqrt(C/(x*y))

Since we want F to be a whole number and strictly less than the above,

F = floor(sqrt(C/(x*y)))

This will give you a new solution A = x*F and B = y*F where A*B < C and A/B = W/H.

mascoj
  • 1,313
  • 14
  • 27
2

mascoj came up with the answer, but here is an interpretation in code form:

#include <utility>
#include <numeric>
#include <cmath>
#include <iostream>

// Mathsy stuff

std::pair<uint64_t, uint64_t> ReduceRatio(const uint64_t W, const uint64_t H)
{
    const double D = std::gcd(W, H);
    return {W/D, H/D};
}

std::pair<uint64_t, uint64_t> Maximise(const uint64_t C, const uint64_t W, const uint64_t H)
{
    const auto [x, y] = ReduceRatio(W, H);
    const uint64_t F = std::floor(std::sqrt(C/double(x*y)));

    const uint64_t A = x*F;
    const uint64_t B = y*F;

    return {A, B};
}


// Test harness

void Test(const uint64_t MaxProduct, const uint64_t W, const uint64_t H)
{
    const auto [NewW, NewH] = Maximise(MaxProduct, W, H);

    std::cout << W << "\u00D7" << H << " (" << (W*H) << " pixels)";

    if (NewW > W)
        std::cout << '\n';
    else
        std::cout << " => " << NewW << "\u00D7" << NewH << " (" << (NewW * NewH) << " pixels)\n";
}

int main()
{
    Test(100000, 1024, 768);
    Test(100000, 1920, 1080);
    Test(500000, 1920, 1080);
    Test(1000000, 1920, 1080);
    Test(2000000, 1920, 1080);
    Test(4000000, 1920, 1080);
}

// g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
// 1024×768 (786432 pixels) => 364×273 (99372 pixels)
// 1920×1080 (2073600 pixels) => 416×234 (97344 pixels)
// 1920×1080 (2073600 pixels) => 928×522 (484416 pixels)
// 1920×1080 (2073600 pixels) => 1328×747 (992016 pixels)
// 1920×1080 (2073600 pixels) => 1872×1053 (1971216 pixels)
// 1920×1080 (2073600 pixels)
Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35