【问题标题】:How quickly check whether double fits in float? (Java)多快检查 double 是否适合浮点数? (爪哇)
【发布时间】:2014-09-29 12:21:22
【问题描述】:

是否有一些算术或按位运算可以检查 双精度是否适合浮点数而不会丢失 精度。

它不应该只检查双范围是 在浮点数范围内,还表示没有尾数位 迷路了。

再见

P.S.:这对 C# 的问题回答了一半: How to check if a double can fit into a float without conversion to infinity 但我需要一个适用于 Java 的解决方案。

【问题讨论】:

  • 为什么这是个好主意?当然,记忆不能成为你的关注点。它的技术依据是什么?
  • 例如在序列化数百万双精度时。如果您可以通过使用浮点数来节省字节,您可能会获得很多速度或空间减少。在慢速网络或慢速媒体上特别有趣

标签: java floating-point


【解决方案1】:

这个怎么样:

double d = ...;
if ((double)(float)d == d) {
   System.out.println(d + " fits into float!");
}

这个想法很简单:我们先转换为float,然后再转换回double,检查结果是否仍然相同。如果 d 不适合浮点数,那么在 (float)d 强制转换时会丢失一些精度,因此结果会有所不同。

严格来说,转换回double 是不必要的,因为比较运算符会隐式执行此转换,所以(float)d == d 也可以。

如果您担心它的性能,因为许多浮点操作比类似的 int 操作慢很多:这在这里几乎不是问题。在现代 CPU 中,浮点数和双精度数之间的转换非常有效。它甚至可以被矢量化!在SSE2 指令集中有cvtpd2pscvtps2pd 指令执行从double 到float 的转换,反之亦然(一次转换4 个值)。这些指令在所有支持它们的 Intel CPU 上都有 4 个周期的延迟。 4 个周期进行 4 次转换非常快。

【讨论】:

  • 投票赞成,因为它是一个解决方案。但是还是有点犹豫要不要用,这意味着两个FOP。
  • @CookieMonster:你的 CPU 的 FPU 通常很快。将double 截断为float 对它来说是一件很容易的事。我怀疑任何神奇的转变(如果存在的话)会比这更快。
  • @CookieMonster:我查看了向量化转换指令的延迟。它们非常好(请参阅答案中我最后编辑的段落)。我没有未矢量化对应物的延迟,但我很确定它们同样快。
  • 这实际上是没有用的,因为它对于大多数小数都失败了。例如0.1 == (float) 0.1 返回false...
  • @NasBanov:是的,因为 0.1 不适合浮点数。所以结果false 是正确的。你的论点是什么?
【解决方案2】:

直接的解决方案可能如下所示:

public class Scribble {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {

            double d = 1d / ((double)i);
            float f = (float) d;

            boolean lossless = d == f;

            System.out.println(d + " can be converted " + (lossless ? "lossless" : "only with loss"));
        }
    }
}

它输出:

1.0 can be converted lossless
0.5 can be converted lossless
0.3333333333333333 can be converted only with loss
0.25 can be converted lossless
0.2 can be converted only with loss
0.16666666666666666 can be converted only with loss
0.14285714285714285 can be converted only with loss
0.125 can be converted lossless
0.1111111111111111 can be converted only with loss
0.1 can be converted only with loss

编辑:速度比较显示,method2 似乎是最快的:

 method1  |  method2  |  method3 
237094654 | 209365345 | 468025911
214129288 | 209917275 | 448695709
232093486 | 197637245 | 448153336
249210162 | 200163771 | 460200921
240685446 | 200638561 | 447061763
332890287 | 337870633 | 450452194
247054322 | 199045232 | 449442540
235533069 | 200767924 | 452743201
256274670 | 199153775 | 453373979
298277375 | 198659529 | 456672251
229360115 | 205883096 | 454198291
252680123 | 224850463 | 452860277
246047739 | 200070587 | 458091501
304270790 | 204517093 | 463688631
235058620 | 204675812 | 448639390
260565871 | 205834286 | 458372075
256008432 | 242574024 | 498943242
311210028 | 208080237 | 478777466
242014926 | 208995343 | 457901380
239893559 | 205111348 | 451616471

代码:

public class Scribble {

    static int size = 1024*1024*100;
    static boolean[] results = new boolean[size];
    static double[] values = new double[size];

    public static void main(String[] args) {

        // generate values
        for (int i = 0; i < size; i++)
            values[i] = 1d / ((double)i);

        long start;
        long duration;

        System.out.println(" method1  |  method2  |  method3 ");
        for (int i = 0; i < 20; i++) {
            start = System.nanoTime();
            method1(size);
            duration = System.nanoTime() - start;
            System.out.printf("%9d", duration);

            start = System.nanoTime();
            method2(size);
            duration = System.nanoTime() - start;
            System.out.printf(" | %9d", duration);

            start = System.nanoTime();
            method3(size);
            duration = System.nanoTime() - start;
            System.out.printf(" | %9d\n", duration);
        }
    }

    private static void method1(int size) {
        boolean[] results = new boolean[size];
        for (int i = 0; i < size; i++) {
            double d = values[i];
            float f = (float) d;

            boolean lossless = d == f;
            results[i] = lossless;
        }
    }

    private static void method2(int size) {
        for (int i = 0; i < size; i++) {
            double d = values[i];
            results[i] = d == (double)(float)d;
        }
    }

    private static void method3(int size) {
        for (int i = 0; i < size; i++) {
            double d = values[i];
            results[i] = Double.compare(d, (float) d) == 0;
        }
    }
}

【讨论】:

  • 假设 d-f == 0 与 d==f 相同。应该在这里吗?
  • 如果使用 d-f == 0,可能需要非正规化。另请参阅:randomascii.wordpress.com/2012/05/20/…“非规范化的需要”。但我仍然不确定这是否也发生在浮动和双精度之间的特定情况下。
  • 这主要测试数字是否可以用二进制浮点数精确表示。 “丢失尾数位”不是一个很好的标准。例如。 1/3 = 0.333333(浮点数)与 0.3333333333333333(双数)。我们失去了任何精确度吗? (例子不是真实的)。
【解决方案3】:

类似于将数字转换为float 并返回到double 并检查是否相等(==),也可以使用Double.compare()

double d = 2/3.0;

// 0 means OK, d fits into float
if (Double.compare(d, (float) d) == 0)
    System.out.println("OK, fits into float.");

此外,由于比较 floatdouble 会隐式地将 float 转换为 double,我们可以简单地写:

if ((float) d == d)
    System.out.println("OK, fits into float.");

【讨论】:

    【解决方案4】:

    如果您想知道您的 double 值是否适合浮点数的 MAX 和 MIN 范围,您不能使用像 (float)d == d 这样的转换,因为 d 可能适合浮点数范围,但不必在转换后具有相同的小数。

    在这种情况下,您必须与Float.MAX_VALUEFloat.MIN_VALUE 进行比较

    return d <= Float.MAX_VALUE && d >= Float.MIN_VALUE;
    

    【讨论】:

    • double 不仅比 float 保存更大/更小的数字,它还具有更高的精度
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-19
    • 2023-03-20
    • 2011-08-13
    • 2021-08-29
    • 2013-11-29
    • 1970-01-01
    相关资源
    最近更新 更多