java实现类似python/c语言中的frexp函数

    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最后被分解为了:

     18 = 0.5625 * 2^5 

      同样的,在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那样直接复用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luffy5459

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值