【问题标题】:How to compare correctly long and float primitives?如何正确比较 long 和 float 基元?
【发布时间】:2015-10-23 01:26:27
【问题描述】:

出于某种原因,我需要比较两个原语:long 和 float。

我可以使用以下代码吗?

long a = 111L;
float b = 111.1f

if (a > b) {
  ...
}

我知道,float 和 float 可以通过使用 epsilon 值等进行比较。

但我怎样才能更正确地对我的案例进行比较?

谢谢大家。

【问题讨论】:

  • 我给了一个答案here可以帮助你。
  • 你如何定义“正确”?上面的代码会告诉你111大于111.1f,这就是我自己所说的“正确”。所以我认为没有任何理由采取不同的做法……

标签: java primitive-types


【解决方案1】:

您可以将它们都包装在 BigDecimal 中并进行比较:

long a = 111L;
float b = 111.1f;
BigDecimal first = new BigDecimal(a);
BigDecimal second = new BigDecimal(b, MathContext.DECIMAL32);
if (first.compareTo(second) > 0) { ... }

为了理解为什么我们有兴趣让两个操作数属于同一类型,让我们深入研究一下JLS 5.6.2 Binary Numeric Promotion

当运算符将二进制数字提升应用于一对操作数时,每个操作数都必须表示一个可转换为数字类型的值,以下规则按顺序适用:

  1. 如果任何操作数是引用类型,则对其进行拆箱 转换(第 5.1.8 节)。

  2. 加宽原始转换(§5.1.2)适用于转换 或以下规则指定的两个操作数:

    • 如果任一操作数的类型为double,则将另一个转换为 double.

    • 否则,如果任一操作数的类型为float,则转换另一个 到float

    • 否则,如果任一操作数的类型为long,则转换另一个 到long

    • 否则,两个操作数都转换为int类型。

由此我们可以得出结论,对于比较a > blong 操作数将被隐式提升为float。然而,这可能会导致精度下降,如JLS 5.1.2 Widening primitive conversion 所述:

intlong 值到 float 或长 值加倍,可能会导致精度损失——即结果 可能会丢失该值的一些最低有效位。在这个 在这种情况下,生成的浮点值将是正确舍入的 整数值的版本,使用 IEEE 754 舍入到最近模式 (§4.2.4)。

【讨论】:

  • 如果使用了float,您可能会考虑适当的舍入。 new BigDecimal(b, MathContext.DECIMAL32);
【解决方案2】:

如果你想使用 epsilon,你可以这样做

// allow for the smallest rounding error
if (a > b + Math.sign(b) * Float.ulp(b))

// assume six digits of precision
static final double ERR = 1e-6;

// allow for the smallest rounding error
if (a > b + Math.sign(b) * b * ERR)

如果您可以假设非负数,则可以删除 Math.sign(b)。

但是,在这种情况下,使用 BigDecimal 可能会更清楚,请参阅@kocko 的回答。

顺便说一句:提高准确性的最简单更改是使用double 而不是floatdouble 的准确度实际上是十亿倍,除非您有数十亿个,否则您使用的提取内存无关紧要。

【讨论】:

  • 在将值与long 进行比较时,floatdouble 的准确性差异不太可能相关。更重要的是,绝对值较大的long 无法在float 中精确表示,但是,如果源值不存在更高的精度,将其转换为double 并不神奇增加更多的精度。但无论如何,我看不出 epsilon 如何改进> 比较。使用 epsilon 对等式测试有意义,但对排序没有意义……
  • @Holger 你对比较是否有意义有什么疑问。即使错误为 1,肯定 a:100 > b:10。错误是否必须为 0 才能在错误范围内说 100 > 10?
  • 好吧,让我们继续看b=10 的例子。原始代码是a>b,从而有效地执行a>10.0。现在您将其替换为a > b + Math.signum(b) * Math.ulp(b),这意味着您将有效地执行a > 10.000000000000002。为什么你认为a > 10.000000000000002 是对a > 10.0 的改进?如果along 无论如何都没有区别,但是如果a 是一个浮点值,那么引入一个epsilon 意味着,如果a 恰好是10.000000000000001,则条件是false。在这里看不到改进。
  • 我误读了你的评论——所以你说你认为1000000100L 不大于1000000000F 的代码是一种改进?我不。如前所述,当有人测试平等时,这是有道理的。然后,有些值不会被认为更大或更小(换句话说,根据 epsilon 相等),但是对于单个“大于”运算符,没有上下文就不可能说哪种行为更好。
  • @Holger 我同意上下文确实很重要。这就是为什么我建议采取保守的方法。即使在相等的情况下,也不能说两个数相等,只能说两个数不相等。
【解决方案3】:

Java 支持直接比较 long 和 float。但是,有一些警告:

如果比较 long 和 float,long 将转换为 float,因为 float 被认为是比 long 更宽的类型。但是,这种转换可能会丢失精度:long 值将被转换为最接近的 float 值;所以对于一个很长的a 和一个浮点数ba > b 可能是假的,尽管a 大于b 如果b 实际上是最接近b 的浮点值。

每一个long和float值都可以用BigDecimal来表示,所以你也可以使用BigDecimal类来比较long和a float:

long a = ...;
float b = ...;
if (BigDecimal.valueOf(a).compareTo(BigDecimal.valueOf(b)) > 0) {
    ...
}

【讨论】:

    【解决方案4】:

    轻松使用此代码:

    Double.compare(longVar * 1., floatVar)
    

    【讨论】:

      猜你喜欢
      • 2018-11-30
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 2021-01-28
      • 2015-03-26
      • 2019-06-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多