【问题标题】:How to calculate the aspect ratio by a given factor?如何通过给定因子计算纵横比?
【发布时间】:2011-11-18 12:34:18
【问题描述】:

如何按给定因子计算纵横比(格式为integer:integer)?

例如,纵横比 16:9 的系数为 1.778,因为 16 / 9 = 1.778。但是我怎样才能找到这个因素的比率呢?所以

Dimension getAspectRatio(double factor) {
    ...
}

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

应该返回

16:9

【问题讨论】:

标签: java math aspect-ratio


【解决方案1】:

这通常是不可能的,因为双精度数可能不代表实际(精确)分数。正如其他答案中所建议的那样,您将不得不依赖启发式或蛮力。

如果你有确切的decimal expansion 和句号,你可以解决它。

这是笔和纸的方式:

  1. 假设您从 1.77777... 开始(这是 16/9,但假设我们不知道)

  2. 您注意到句点是7(一位数),因此您乘以 10(即将小数点向右移动一位):

    10n = 17.77777...
    
  3. 您现在可以通过计算10n - n 来取消重复部分:

    10n - n = 17.77777... - 1.77777... = 16
    
  4. 求解n 得到n = 16/9

将其转换为代码需要您弄清楚十进制扩展的句点的开始和长度,这本身就是一个令人讨厌的问题,因为该数字通常看起来像 0.16666667

【讨论】:

  • 感谢您的解释。这个数字确实没有准确表示。我猜你的方式或者 Lukas Eder 的方式是最好的。
【解决方案2】:

这是一个线性方程。一般来说,线性方程中不能有两个未知数

【讨论】:

    【解决方案3】:

    免责声明:这些算法愚蠢且效率低下。我敢肯定还有更好的...

    找到近似值的一个愚蠢、直接(不是很有效)的算法是这样的:

    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); 
    

    输出。

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

    更新:替代算法

    我刚刚想到了一种替代算法,它试图接近近似值。当然,它仍然不是很有效......

    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);
    

    输出是一样的

    如果我偶然发现了一个有效的算法,我会在这里发布:-)

    【讨论】:

    • 我认为应该以其他方式更好地评估近似值的质量:它是delta/minstep,其中 minstep 是 1/分母。这样我们就可以避免选择 1778/1000 作为更好的近似值。实际上,在给定分母的情况下,我们至少与minstep 一样准确,因此我们检查在明显准确度范围内的近似值有多好。
    • 就像 aioobe 所说,由于四舍五入,您将无法获得确切的比率,但@lukas 可能是您最好的解决方案。尽管如果您使用时髦的因素,即使这也行不通,例如5.941148 是 101/17(我刚刚选择了一个素数和一个超过 100 的数字,所以这将在循环之外 - 不是真正的实际显示尺寸,只是说明一个点)。
    • 而且您不需要遍历分子:您可以只选择分母并计算最佳匹配的分子(如我删除的答案)。
    • @Windle:由于这是对常见整数对执行的操作的逆操作,我认为这是一个足够接近的操作。 16:9 真的不是一个任意数字,所以我猜 OP 将能够找到屏幕、电视等最相关的纵横比……再说一次,“傻”这个词意味着这是一个“傻”解决方案:-)
    • @Lukas 只是说清楚我喜欢你的答案(实际上是在你发布你的答案时写几乎相同的东西)只是想确保 OP 意识到这将是艰难的(不可能在练习?)将其用于所有可能的整数纵横比。
    【解决方案4】:

    实际上,a/b 形式的所有因子都表示为有限比率或无限但周期性比率(假设ab 是整数)。不过,期间可能会很大。如果 period 至少比双精度低一半,您可以尝试检测它并找到确切的比率。或者你可以试着做出最好的猜测。

    【讨论】:

      【解决方案5】:

      纵横比可以是实数(例如1.85:1),所以恐怕无法从因子“猜测”纵横比。

      但可能有 10 种常用的纵横比。您可以轻松制作因子纵横比表。

      【讨论】:

      【解决方案6】:

      这是一个非常晚的回复,但我已经用一种更简单的方法解决了这个问题,我知道其他人会很感激的。

      我假设您已经知道屏幕分辨率,因为您知道纵横比(十进制等效值)。您可以通过求解屏幕宽度和高度之间的最大公约数来找到纵横比(整数:整数)。

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

      这将返回屏幕宽度和高度之间的最大公因数。要找到实际的纵横比,只需将屏幕宽度和高度除以最大公因数。所以...

      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;
      

      这个输出:

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

      希望这会有所帮助。

      注意:这不适用于某些分辨率。评论包含更多信息。

      【讨论】:

      • 对不起!函数greaterCommonFactor中的a和b是什么?
      • @ScottChu 好问题,我犯了一个错误。我已经修改了我的答案。感谢您提请我注意。
      • 这不适用于像 1366x768 这样在给定方向上只有几个或多或少像素的常见分辨率。请看stackoverflow.com/a/13466237/2153190
      • 问题是另一种方式。给定一个纵横比,找出宽度和高度。
      【解决方案7】:

      这是 Scala 中的一个实现,它根据 Farey 序列找到 Best rational approximation。该算法由@AakashM 提出,由John D. Cook's Python implementationDavid 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)
      }
      

      我创建了一个gist,其中还包含一些测试。

      【讨论】:

        【解决方案8】:

        如果有人想根据纵横比和对角线计算电视的高度和宽度,请使用以下代码。

        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));
        
                }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-12
          • 2010-11-14
          • 2013-06-13
          • 1970-01-01
          • 2013-10-31
          • 2013-08-26
          相关资源
          最近更新 更多