frexp这个函数在c语言和python中都是math模块自带的函数,这个函数在java中不存在。这个问题似乎在网上也很难找到相关描述。
frexp这个函数的作用就是把一个数分解为尾数mantissa、基底base、指数exponent的形式,一般分解,默认base为10或者2,更多的时候,默认是2,比如c/python中的frexp函数的实现。
下面是一个python的例子。
>>> import math
>>> m,e = math.frexp(18)
>>> m,e
(0.5625, 5)
>>> m * 2 ** e
18.0
>>>
数字18最后被分解为了:
同样的,在c语言中,可以得出一样的结果:
#include <stdio.h>
#include <math.h>
int main(){
double x = 18,m;
int e;
m = frexp(x,&e);
printf("%.2lf = %.4lf * 2 ^ %d\n",x,m,e);
return 0;
}
运行代码截图:
这样的分解,有一个特点,就是尾数mantissa是一个(0.5,1)之间的数字。
java中的实现,可以参考如下代码:
public class FrexpDemo {
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;
}
public static void main(String[] args) {
FRexpResult r = frexp(18);
System.out.println(r);
}
}
class FRexpResult {
public int exponent = 0;
public double mantissa = 0.;
@Override
public String toString() {
return String.format("mantissa=%f,exponent=%d", mantissa,exponent);
}
}
经过简单的验证,可以得出与python/c类似的结果。
===============================================
另外,还有一种实现方法,代码如下:
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 = 18.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.print(frexp.mantissa + " * 2^" + frexp.exponent + " = ");
System.out.println(frexp.mantissa * (1 << frexp.exponent));
}
}
运行结果类似:
这个分解的作用是什么,一般情况下,可能用不上,直到我在使用asn1解析实数类型的时候,发现不管是c语言库asn1c,还是python的asn1tools工具,他们在对实数类型编码的时候,都是采用了将数据分解为一个mantissa * base(2) ^ exponent的方式的时候,发现这个frexp分解还是有用的。
通过上面的示例,我们发现,mantissa是一个介于(0.5,1)之间的浮点数,而asn1编解码,都是将这个数再次放大到了整数。比如:
18 = 9 * 2^1 // mantissa = 9 exponent = 1
20 = 5 * 2^2 // mantissa = 5 exponent = 2
19 = 19* 2^0 // mantissa = 19 exponent = 0
要达到mantissa是一个整数,还是需要用到上面的frexp函数的。
如下所示,这段代码是asn1tools工具中对实数的编码操作,就是将实数分解为mantissa * 2 ^ exponent的形式,然后只存储了mantissa,exponent的字节码。直接把基数2忽略了,这里的mantissa就是整数,就是用frexp来先把实数分解为初步的mantissa * 2 ^ exponent的形式,再通过各种操作将mantissa放大到整数。
import math
import binascii
def lowest_set_bit1(value):
offset = (value & -value).bit_length() - 1
if offset < 0:
offset = 0
return offset
def encode_real(data):
if data == float('inf'):
data = b'\x40'
elif data == float('-inf'):
data = b'\x41'
elif math.isnan(data):
data = b'\x42'
elif data == 0.0:
data = b''
else:
if data >= 0:
negative_bit = 0
else:
negative_bit = 0x40
data *= -1
mantissa, exponent = math.frexp(abs(data))
mantissa = int(mantissa * 2 ** 53)
lowest_set_bit = lowest_set_bit1(mantissa)
mantissa >>= lowest_set_bit
mantissa |= (0x80 << (8 * ((mantissa.bit_length() // 8) + 1)))
mantissa = binascii.unhexlify(hex(mantissa)[4:].rstrip('L'))
exponent = (52 - lowest_set_bit - exponent)
if -129 < exponent < 128:
exponent = [0x80 | negative_bit, ((0xff - exponent) & 0xff)]
elif -32769 < exponent < 32768:
exponent = ((0xffff - exponent) & 0xffff)
exponent = [0x81 | negative_bit, (exponent >> 8), exponent & 0xff]
else:
raise NotImplementedError(
'REAL exponent {} out of range.'.format(exponent))
data = bytearray(exponent) + mantissa
return data
这段代码用java也可以实现,但是要复杂很多,很多数据类型需要重新定义,并不能像python那样直接复用。