7

How do I calculate the aspect ratio (formatted as integer:integer) by a given factor?

For example, aspect ratio 16:9 has a factor of 1.778, because 16 / 9 = 1.778. But how can I find the ratio by that factor? So

Dimension getAspectRatio(double factor) {
    ...
}

public static void main(String[] arguments) {
    Dimension d = getAspectRatio(16d / 9d);
    System.out.println(d.width + ":" + d.height);
}

should return

16:9
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • 4
    You may be well served by finding the [best rational approximation](http://www.johndcook.com/blog/2010/10/20/best-rational-approximation) to your input – AakashM Sep 16 '11 at 09:02

8 Answers8

12

This is an extremely late reply, but I have solved this using a much easier way and I know other's would appreciate it.

I'm assuming that you already know the screen resolution since you know the aspect ratio (decimal equivalent). You can find the aspect ratio (integer:integer) by solving for the greatest common factor between the screen width and height.

public int greatestCommonFactor(int width, int height) {
    return (height == 0) ? width : greatestCommonFactor(height, width % height);
}

This will return the greatest common factor between the screen width and height. To find the actual aspect ratio, you just divide the screen width and height by the greatest common factor. So...

int screenWidth = 1920;
int screenHeight = 1080;

int factor = greatestCommonFactor(screenWidth, screenHeight);

int widthRatio = screenWidth / factor;
int heightRatio = screenHeight / factor;

System.out.println("Resolution: " + screenWidth + "x" + screenHeight;
System.out.println("Aspect Ratio: " + widthRatio + ":" + heightRatio;
System.out.println("Decimal Equivalent: " + widthRatio / heightRatio;

This outputs:

Resolution: 1920x1080
Aspect Ratio: 16:9
Decimal Equivalent: 1.7777779

Hope this helps.

Note: This won't work for some resolutions. Comments contain more info.

Chad Greenburg
  • 345
  • 7
  • 14
  • 1
    Excuse me! What're the a and b in the fucntion greatestCommonFactor? – Scott Chu Apr 22 '15 at 09:02
  • @ScottChu Great question, I made a mistake. I've revised my answer. Thanks for bringing that to my attention. – Chad Greenburg Apr 22 '15 at 14:10
  • This won't work for common resolutions like 1366x768 that have just a few more or less pixels in a given direction. Please see http://stackoverflow.com/a/13466237/2153190 – akkie Oct 13 '15 at 10:17
  • 1
    The question is going the other way. Given an aspect ratio, find the width and height. – Teepeemm Nov 16 '15 at 01:14
10

Disclaimer: These algorithms are silly and inefficient. I'm sure there's a better one...

A silly, straightforward (not very efficient) algorithm to find an approximation is this:

double ratio = 1.778;
double bestDelta = Double.MAX_VALUE;
int bestI = 0;
int bestJ = 0;

for (int i = 1; i < 100; i++) {
  for (int j = 1; j < 100; j++) {
    double newDelta = Math.abs((double) i / (double) j - ratio);
    if (newDelta < bestDelta) {
      bestDelta = newDelta;
      bestI = i;
      bestJ = j;
    }
  }
}

System.out.println("Closest ratio: " + bestI + "/" + bestJ);
System.out.println("Ratio        : " + ((double) bestI / (double) bestJ));
System.out.println("Inaccurate by: " + bestDelta); 

Output.

Closest ratio: 16/9
Ratio        : 1.7777777777777777
Inaccurate by: 2.2222222222234578E-4

Update: Alternative algorithm

I've just thought of an alternative algorithm, which tries to close in on the approximation. Of course, it's still not very efficient...

double bestDelta = Double.MAX_VALUE;
int i = 1;
int j = 1;
int bestI = 0;
int bestJ = 0;

for (int iterations = 0; iterations < 100; iterations++) {
  double delta = (double) i / (double) j - ratio;

  // Optionally, quit here if delta is "close enough" to zero
  if (delta < 0) i++;
  else j++;

  double newDelta = Math.abs((double) i / (double) j - ratio);
  if (newDelta < bestDelta) {
    bestDelta = newDelta;
    bestI = i;
    bestJ = j;
  }
}

System.out.println("Closest ratio: " + bestI + "/" + bestJ);
System.out.println("Ratio        : " + ((double) bestI / (double) bestJ));
System.out.println("Inaccurate by: " + bestDelta);

The output is the same

Should I stumble upon an efficient algorithm, I'll post it here :-)

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • I think the quality of the approximation should be better evaluated slightly other way: it's `delta/minstep`, where minstep is 1/denominator. This way we avoid for example choosing 1778/1000 as a better approximation. Indeed, with a given denominator we are at least as accurate as `minstep`, so we check how good is the approximation within the obvious accuracy. – Vlad Sep 16 '11 at 09:04
  • Like aioobe said, you won't be able to get the exact ratios due to rounding, but @lukas has probably your best solution. Though even this won't work if you use a funky factor, e.g. 5.941148 which is 101/17 (I just picked a prime and a number over 100 so that is would be outside the loop - not really a practical display size, just illustrating a point). – Windle Sep 16 '11 at 09:05
  • And you don't need to loop over the numerator: you can choose only the denominator and calculate the best matching numerator (as in my deleted answer). – Vlad Sep 16 '11 at 09:07
  • @Windle: Since this is the reverse of an operation performed on common integer pairs, I thought it's a close enough operation. `16:9` really isn't an arbitrary number, so I guess the OP will be able to find most relevant aspect ratio's for screens, tv's, etc... Then again, the word "silly" implies that this is a "silly" solution :-) – Lukas Eder Sep 16 '11 at 09:15
  • @Lukas Just to be clear I liked your answer (was actually about to write up pretty much the same thing when you posted yours lol) Just wanted to make sure OP realized it would be tough (impossible in practice?) to use this for EVERY possible integer aspect ratio. – Windle Sep 16 '11 at 09:40
  • Thanks a lot, it works! It's indeed not that fast, but due to rounding, it's not an exact number. So I guess this is the only way. – MC Emperor Sep 22 '11 at 12:48
  • If the ratio is 1.0 the closest ratio "2/2" is returned, which should be "1/1" of course. So a short cut can be added: if (ratio == 0.0) return "1/1" – rmuller May 13 '20 at 11:20
7

This is not possible in general, since a double may not represent the actual (exact) fraction. You'll have to rely on heuristics or brute force as suggested in the other answers.

If you had the exact decimal expansion and period you could solve it though.

Here's the pen and paper way:

  1. Suppose you start with 1.77777... (which is 16/9 but let's assume that we didn't know that)

  2. You note that the period is 7 (one digit) so you multiply by 10 (i.e. move the decimal point one step to the right):

    10n = 17.77777...
    
  3. You can now cancel out the repeating part by computing 10n - n:

    10n - n = 17.77777... - 1.77777... = 16
    
  4. Solving for n yields n = 16/9

Translating this to code would require you to figure out the start and length of the period of the decimal expansion which will itself be a nasty problem, as the number could typically look something like 0.16666667.

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • Thanks for the explaination. The number is indeed not represented exactly. I guess your way or the one of Lukas Eder is the best. – MC Emperor Sep 22 '11 at 12:46
3

Here is an implementation in Scala which finds the Best rational approximation based on the Farey sequence. This algorithm was suggested by @AakashM and it's translated from John D. Cook's Python implementation and David Weber's C++ modification.

/**
 * Calculates the `Best rational approximation` based on the Farey sequence.
 *
 * Translated from John D. Cook's Python implementation and David
 * Weber's C++ modification.
 *
 * @param x A value to be approximated by two integers.
 * @param eps The required precision such that abs(x-a/b) < eps. Eps > 0.
 * @param n The maximum size of the numerator allowed.
 * @return The best rational approximation for x.
 */
def farey(x: Double, eps: Double, n: Int): (Int, Int) = {

  @tailrec
  def iterate(a: Int, b: Int, c: Int, d: Int): (Int, Int) = {
    if (b <= n && d <= n) {
      val mediant = (a + c).toDouble / (b + d).toDouble
      if (Math.abs(x - mediant) < eps) {
        if (b + d <= n) {
          (a + c) -> (b + d)
        } else if (d > b) {
          c -> d
        } else {
          a -> b
        }
      } else if (x > mediant) {
        iterate(a + c, b + d, c, d)
      } else {
        iterate(a, b, a + c, b + d)
      }
    }
    else if (b > n) c -> d
    else a -> b
  }

  iterate(0, 1, 1, 0)
}

I've created a gist which contains also some tests.

akkie
  • 2,523
  • 1
  • 20
  • 34
1

This is a linear equation. In general you can't have two unknowns in a linear equation.

Suraj Chandran
  • 24,433
  • 12
  • 63
  • 94
1

Actually, all the factors of form a/b are presented as finite ratios or infinite but periodic ratios (provided a and b are integers). Period can be pretty big, though. You could try to detect it and find exact ratio if period is, at least, half less than double precision. Or you could try to make best guess.

pupssman
  • 541
  • 2
  • 11
0

If someone wants to calculate the tv height and width based on aspect ratio and diagonal use the below code.

public void printTvHeightAndWidth(){
            int widhtRatio = 16;
            int heightRatio = 9;
            int diagonal = 88;

            double tvHeight = (heightRatio * diagonal) / Math.sqrt(widhtRatio * widhtRatio + heightRatio * heightRatio);
            double tvWidth = (widhtRatio * diagonal) / Math.sqrt(widhtRatio * widhtRatio + heightRatio * heightRatio);
            DecimalFormat df = new DecimalFormat("#.##");
            System.out.println("W = " + df.format(tvWidth) + " H = " + df.format(tvHeight));

        }
ManojP
  • 6,113
  • 2
  • 37
  • 49
0

Aspect ratio can be be real number (1.85:1 for example), so I'm afraid its impossible to "guess" aspect ration from factor.

But there are maybe 10 common used aspect ratios. You could easy make factor-aspect ratio table.

bigGuy
  • 1,732
  • 1
  • 22
  • 37