Java中是否有类似于frexp的函数?

6

是否有Java等效的C / C ++函数frexp? 如果您不熟悉,frexp被维基百科定义为“将浮点数分解为尾数和指数。”

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

这是第一篇参考文献中的代码示例。 它应该使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
@Carl,我同意这将会很有用。不过,我非常了解我自己和我的工作量,所以我不打算承诺自己尝试单独创建它。我相信我可以在我有投入的时间内完成80%的工作且正确率为80%,但这几乎等于没用。 - Bob Cross
真正的问题是:为什么frexp不把浮点数分解成两个整数,而是至少需要一个浮点数.. 对于一种没有意义的浮点数分解来说(考虑到递归.....) - Nils Pipenbrinck
@Nils,实际上没有递归。结果值被定义为介于0.5和1.0之间。在这种情况下,指数将始终为零。 - Bob Cross
6个回答

3
这个怎么样?
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;
}

这段内容是从一篇类似的C#问题的答案中“灵感”或者说几乎完全复制而来。它使用比特位操作,然后将尾数转换为介于1.0和0.0之间的数字。

1
哎呀!上面的代码并不完全正确:它应该是 while(realMant >= 1.0) 而不是 while(realMant > 1.0)。返回值的数量级必须在1/2(含)到1(不含)的范围内,参见GNU libc 手册。通过上面的代码,frexp(1.0)会错误地返回1.0而不是0.5。 - akbertram

2

这确实能够满足您的需求。

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 = -1; // Thx Kevin
        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浮点标准的位而不是试图推导出数学结果。这就是这个问题的目标。 - Bob Cross
2
亲爱的jitter(哇,12年前):我刚刚运行了一些frexp()与你的解决方案等价性测试,并且,在修复了一个小错误后,恭喜,它可以工作!唯一的错误是“sign--”,它应该改为“sign = -1”。非常感谢,我们有一个客户正在移植我们的C#代码,他们不能使用MSVCRT.dll,所以我们不得不重新编写frexp()。 - Kevin North
1
@KevinNorth 谢谢,已更新答案。 - jitter

1
请参考 Float.floatToIntBits 和 Double.doubleToLongBits 方法。为了解码 IEEE 754 浮点数,您仍需要一些额外的逻辑。

谢谢 - 我知道可以获取位。我关心的不是从位集解析s,e和m的基本情况。我更担心的是拥有一个完整的frexp实现,以维护处理所有角落案例(例如不同类型的NaN)的契约。 - Bob Cross

0

如果我理解得正确的话...

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标准浮点表示并对其进行解码。目标不是提出一个似乎给出相同答案的数学表达式。 - Bob Cross

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

-1

目前在Java核心或Commons Lang(最有可能的其他地方)中,没有与frexp具有完全相同功能和易用性的实现。如果存在这样的实现,那么它可能在一个不常用的工具包中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接