【问题标题】:Binary search / bisection for floating point numbers浮点数的二进制搜索/二分法
【发布时间】:2017-07-08 21:49:35
【问题描述】:

用二分查找很容易找到一个整数even if it can be arbitrarily large:先猜数量级,然后不断除以区间。 This answer 描述了如何找到任意有理数。

设置好场景后,我的问题类似:我们如何猜测 IEEE 754 浮点数?假设它不是 NaN,但其他一切都是公平的游戏。对于每个猜测,您的程序将被告知所讨论的数字是更高、相等还是更低。尽量减少最坏情况下所需的猜测次数。

(这不是家庭作业。不过,我可能会做一个,如果结果证明这是一个有趣的答案,而不仅仅是“通过大量特殊情况处理将浮点数值困难打死。” )

编辑:如果我在搜索方面做得更好,我可以找到answer---但这只有在您已经知道重新解释为int 有效(有某些警告)时才有效。所以留下这个。感谢 Harold 的精彩回答!

【问题讨论】:

  • 我们是否可以将浮点数的位模式重新解释为整数?
  • 当然,随便你。但是当解释为浮点数时,oracle 会给你比较结果。
  • 为什么要“先猜数量级”?这和二分查找有什么关系?
  • @HenkHolterman 看到我添加的新链接,我希望问题更清楚?摘要:要使用经典的二进制搜索,您首先需要有限边界。数学意义上的整数没有先验的有限界限,它可以任意大或小。因此,您需要先猜出它们,然后才能开始划分区间。
  • @Matthias:不需要。这不是太重要。我只是说我不会称它为搜索,虽然原理是一样的:反复切半。

标签: algorithm floating-point binary-search numerics


【解决方案1】:

IEEE-754 64 位浮点数实际上是 64 位表示。此外,除了 NaN 值之外,浮点比较和正值的整数比较没有区别。 (也就是说,符号位未设置的两个位模式将产生相同的比较结果,无论您将它们作为int64_t 还是double 进行比较,除非其中一个位模式是浮点 NaN-。)

这意味着您可以通过一次猜测一位来在 64 次猜测中找到一个数字,即使该数字是 ±∞。首先将数字与0进行比较;如果目标是“更少”,则以与以下相同的方式产生猜测,但在猜测之前将它们取反。 (由于 IEEE-754 浮点数是符号/幅度,您可以通过将符号位设置为 1 来否定该数字。或者您可以进行正位模式重新解释,然后浮点否定结果。)

之后,每次猜测一位,从最高阶值位开始。如果数字大于或等于猜测值,则将该位设置为 1;如果数字较小,则将该位设置为 0;并继续下一点,直到不再有。要构建猜测,请将位模式重新解释为 double

有两个注意事项:

  1. 您无法通过比较测试区分 ±0。这意味着如果你的对手想让你区分它们,他们将不得不为你提供一种询问与 -0 是否相等的方法,并且在你显然确定数字为 0 之后,你必须使用该机制(这将发生在第 64 次猜测)。这将增加一个猜测,总共 65 个。

  2. 如果您确定目标不是 NaN,则没有其他问题。如果它可能是一个 NaN,你需要小心你如何比较:如果你总是问“X 小于这个猜测吗?”事情会很好,因为 NaN 比较总是返回 false .这意味着在连续 11 个“否”答案(不包括建立符号的那个)之后,您会发现自己在猜测 ∞,假设如果数字不小于 ∞,则它必须相等。但是,仅在这种情况下,您还需要显式测试是否相等,因为如果目标是 NaN,这也是错误的。这不会在计数中增加额外的猜测,因为它总是会在 64 次猜测用完之前很久。

【讨论】:

  • 关于你的第一个警告:假设我们每次都得到一个三路结果(更高/更低/相等),并且第一次比较是与 0.0,不会确定数字是 + /- 0 在第一次猜测时发生,所以 +0.0 或 -0.0 的结果可能在第二次猜测时发生?
  • 太棒了!我什至很容易看到该算法如何不仅终止,而且具有最佳的最坏情况运行时间。能否加个背景链接保存比较下重新解释为int64_t,好吗?
  • 我用 Python 实现了你的想法。很有魅力。我可以在 64 步中猜出每个浮点数。 (还有一些在 63 个步骤中失败的例子。)
【解决方案2】:

同样的方法可以应用于浮点数。最坏情况下的运行时间是 O(log n)。

public class GuessComparer
{
    private float random;
    public GuessComparer() // generate a random float and keep it private
    {
        Random rnd = new Random();
        var buffer = new byte[4];
        rnd.NextBytes(buffer);
        random = BitConverter.ToSingle(buffer, 0);
    }
    public int CheckGuess(float quess) // answer whether number is high, lower or the same.
    {
        return random.CompareTo(quess);
    }
}
public class FloatFinder
{

    public static int Find(GuessComparer checker)
    {
        float guess = 0;
        int result = checker.CheckGuess(guess);
        int guesscount = 1;
        var high = float.MaxValue;
        var low = float.MinValue;
        while (result != 0)
        {
            if (result > 0) //random is higher than guess
                low = guess;
            else// random is lower than guess

                high = guess;

            guess = (high + low) / 2;
            guesscount++;
            result = checker.CheckGuess(guess);
        }
        Console.WriteLine("Found answer in {0}", guesscount);
        return guesscount;
    }

    public static void Find()
    {
        var checker = new GuessComparer();
        int guesses = Find(checker);
    }
}

【讨论】:

  • 谢谢。我很惊讶这可以处理溢出和下溢等所有令人讨厌的问题?我不认为这可以处理无穷大吗?我怀疑这是最优的,因为它对浮点密度一无所知(即它没有考虑到 0 和 1 之间的浮点数比 1,000,000 和 1,000,001 之间的浮点数更多。
  • 不需要。只要起始高位是数据类型的最大值(单次为 3.40282347E+38),起始低位是最小值(-3.40282347E+38),在每个循环上取高低之间的平均值即可使算法和整数一样收敛。不可能遇到正无穷大 (1.0F / 0.0F)、负无穷大 (-1.0F / 0.0F) 或 NaN (0.0F / 0.0F)。
  • 如果要猜测的数字无穷大怎么办?或者,如果有问题的数字刚好低于 float.MaxValue 怎么办?当“低”也很大时,您对 (high + low)/2 的计算不会接近结束吗?
  • 单曲最大值是正无穷大,不是3.40282347E+38。
猜你喜欢
  • 1970-01-01
  • 2015-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-16
  • 2017-01-05
相关资源
最近更新 更多