【问题标题】:Is there a Java equivalent of frexp?有没有相当于 frexp 的 Java 版本?
【发布时间】:2010-12-05 21:09:32
【问题描述】:

是否存在与 C/C++ 函数等效的 Java 函数 frexp?如果您不熟悉,则 frexp 是 defined by Wikipedia,用于“将浮点数分解为尾数和指数”。

我正在寻找一种速度和准确性兼备的实现,但如果我只能选择一个,我宁愿获得准确性。

这是来自第一个参考的代码示例。它应该使 frexp 合约更加清晰:

/* frexp example */
#include <stdio.h>
#include <math.h>

int main ()
{
  double param, result;
  int n;

  param = 8.0;
  result = frexp (param , &n);
  printf ("%lf * 2^%d = %f\n", result, n, param);
  return 0;
}

/* Will produce: 0.500000 * 2^4 = 8.000000 */

【问题讨论】:

  • 我认为 Apache Commons Math 包可能是找到这个的好地方,但我没有在其中看到任何东西。也许您可以为此提出功能请求?或者,如果您决定自己编写代码,请与他们讨论将其包含在库中 - 对我来说似乎是一个有用的补充。
  • @Carl,我同意它会很有用。不过,我非常了解自己和我的工作量,所以我不会致力于尝试自己创建它。我确信我可以用我必须投入的时间正确地完成 80% 的工作 80%,这几乎比无用更糟糕......
  • 真正的问题是:为什么 frexp 不会将浮点数分解为两个整数,但至少需要一个浮点数。对于没有意义的浮点分解(想想递归...... )
  • @Nils,实际上,没有任何递归。结果值定义在 0.5 和 1.0 之间。在这种情况下,指数将始终为零。

标签: java c++ c floating-point math.h


【解决方案1】:

怎么样?

public static class FRexpResult
{
   public int exponent = 0;
   public double mantissa = 0.;
}

public static FRexpResult frexp(double value)
{
   final FRexpResult result = new FRexpResult();
   long bits = Double.doubleToLongBits(value);
   double realMant = 1.;

   // Test for NaN, infinity, and zero.
   if (Double.isNaN(value) || 
       value + value == value || 
       Double.isInfinite(value))
   {
      result.exponent = 0;
      result.mantissa = value;
   }
   else
   {

      boolean neg = (bits < 0);
      int exponent = (int)((bits >> 52) & 0x7ffL);
      long mantissa = bits & 0xfffffffffffffL;

      if(exponent == 0)
      {
         exponent++;
      }
      else
      {
         mantissa = mantissa | (1L<<52);
      }

      // bias the exponent - actually biased by 1023.
      // we are treating the mantissa as m.0 instead of 0.m
      //  so subtract another 52.
      exponent -= 1075;
      realMant = mantissa;

      // normalize
      while(realMant > 1.0) 
      {
         mantissa >>= 1;
         realMant /= 2.;
         exponent++;
      }

      if(neg)
      {
         realMant = realMant * -1;
      }

      result.exponent = exponent;
      result.mantissa = realMant;
   }
   return result;
}

这是“灵感”,或者实际上几乎是从answer 复制到类似的 C# 问题。它与位一起工作,然后使尾数成为 1.0 和 0.0 之间的数字。

【讨论】:

  • 哎呀!上面的代码不太正确:应该是 while(realMant >= 1.0) 而不是 while(realMant > 1.0)。返回值的大小必须在 1/2(含)到 1(不含)的范围内,请参阅GNU libc manual。使用上面的代码, frexp(1.0) 将错误地返回 1.0 而不是 0.5。
【解决方案2】:

如果我没看错……

public class Frexp {
  public static void main (String[] args)
  {
    double param, result;
    int n;

    param = 8.0;
    n = Math.getExponent(param);
    //result = ??

    System.out.printf ("%f * 2^%d = %f\n", result, n, param);
  }
}

不幸的是,似乎没有内置方法可以在不先将尾数转换为 BigDecimal 的情况下获取尾数(或者只是进行除法:result = param / Math.pow(2,n)

奇怪的是,scalb 恰恰相反:取一个尾数和指数,并从中生成一个新的浮点数。

【讨论】:

  • @R. Bemrose,练习的重点不是转换。相反,该函数采用 IEEE 标准浮点表示并对其进行解码。我们的目标不是想出一个似乎给出相同答案的数学表达式。
【解决方案3】:

不,在核心 Java 或 Commons Lang(很可能在其他地方找到它)中没有当前实现具有与 freexp 完全相同的功能和易用性;我知道的。如果它确实存在,它可能在一个未被广泛使用的工具包中。

【讨论】:

    【解决方案4】:

    这确实可以满足您的需求。

    public class Test {
      public class FRex {
    
        public FRexPHolder frexp (double value) {
          FRexPHolder ret = new FRexPHolder();
    
          ret.exponent = 0;
          ret.mantissa = 0;
    
          if (value == 0.0 || value == -0.0) {
            return ret;
          }
    
          if (Double.isNaN(value)) {
            ret.mantissa = Double.NaN;
            ret.exponent = -1;
            return ret;
          }
    
          if (Double.isInfinite(value)) {
            ret.mantissa = value;
            ret.exponent = -1;
            return ret;
          }
    
          ret.mantissa = value;
          ret.exponent = 0;
          int sign = 1;
    
          if (ret.mantissa < 0f) {
            sign--;
            ret.mantissa = -(ret.mantissa);
          }
          while (ret.mantissa < 0.5f) {
            ret.mantissa *= 2.0f;
            ret.exponent -= 1;
          }
          while (ret.mantissa >= 1.0f) {
            ret.mantissa *= 0.5f;
            ret.exponent++;
          }
          ret.mantissa *= sign;
          return ret;
        }
      }
    
      public class FRexPHolder {
        int exponent;
        double mantissa;
      }
    
      public static void main(String args[]) {
        new Test();
      }
    
      public Test() {
        double value = 8.0;
        //double value = 0.0;
        //double value = -0.0;
        //double value = Double.NaN;
        //double value = Double.NEGATIVE_INFINITY;
        //double value = Double.POSITIVE_INFINITY;
    
        FRex test = new FRex();
        FRexPHolder frexp = test.frexp(value);
        System.out.println("Mantissa: " + frexp.mantissa);
        System.out.println("Exponent: " + frexp.exponent);
        System.out.println("Original value was: " + value);
        System.out.println(frexp.mantissa+" * 2^" + frexp.exponent + " = ");
        System.out.println(frexp.mantissa*(1<<frexp.exponent));
      }
    }
    

    【讨论】:

    • @jitter,谢谢,但 frexp 实际上与 IEEE 浮点标准的位一起工作,而不是试图推断出数学结果。这就是这个问题的目标。
    • 亲爱的 jitter(哇,12 年前):我刚刚运行了一些 frexp() 与您的解决方案等效性测试,并且在修复了一个小错误之后,恭喜,它有效!一个错误是“sign--”,它应该改为“sign = -1”。非常感谢,我们有一个客户端正在移植我们的 C# 代码,但他们不能使用 MSVCRT.dll,所以我们不得不重新编写 frexp()。
    【解决方案5】:

    请参阅 Float.floatToIntBits 和 Double.doubleToLongBits。您仍然需要一些额外的逻辑来解码 IEEE 754 浮点数。

    【讨论】:

    • 谢谢 - 我知道有能力得到一点。我关心的不是从位集中解析 s、e 和 m 的基本情况。我更担心有一个完整的 frexp 实现来维护处理所有极端情况的合同(例如,不同风格的 NaN)。
    【解决方案6】:

    我不熟悉 frexp 函数,但我认为您需要查看 BigDecimal' 缩放和未缩放的值。 'unscaled' 是精度尾数,比例是指数。在伪代码中: value = unscaledValue 10^(-scale)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-24
      • 1970-01-01
      • 1970-01-01
      • 2016-11-03
      • 1970-01-01
      • 2022-11-21
      相关资源
      最近更新 更多