这个问题的兴趣,源自我在解析TCP/UDP数据协议的时候遇到的一个问题:
这个数字,我是通过当前日期的时间微秒来获取的。
以下是c语言来将这个时间转字节数组:
long long t = 1616491310637
double x;
unsigned char buf[8];
memcpy(buf,&t,sizeof(buf));
//[0x2d, 0xee, 0x63, 0x5e, 0x78, 0x01, 0x00, 0x00]
//小端序表示的字节序
通过计算器计算16进制表示:
在java里面,byte[]直接转long类型,不会有什么问题。而且非常巧合的是,byte[]转double竟然和byte[]转long类型有很相似的地方:
package com.xxx.huali.hualitest;
public class DataTransfer {
private static final char[] HEX_STR = "0123456789ABCDEF".toCharArray();
public static long bytes2Long(byte[] data) {
long value = 0;
for(int i=7;i>=0;i--) {
value <<= 8;
value |= (data[i]&0xff);
}
return value;
}
public static double bytes2Double(byte[] data) {
long value = 0;
for(int i=7;i>=0;i--) {
value <<= 8;
value |= (data[i]&0xff);
}
return Double.longBitsToDouble(value);
}
public static byte[] long2Bytes(long value) {
byte[] data = new byte[8];
for(int i=7;i>=0;i--) {
data[i] = (byte)((value >> i*8) & 0xff);
}
return data;
}
public static byte[] double2Bytes(double value) {
long d = Double.doubleToRawLongBits(value);
byte[] data = new byte[8];
for(int i=7;i>=0;i--) {
data[i] = (byte)((d >> i*8) & 0xff);
}
return data;
}
public static String num2Hex(byte b) {
StringBuffer sb = new StringBuffer();
sb.append(HEX_STR[(b&0xf0)>>4]);
sb.append(HEX_STR[b&0x0f]);
return sb.toString().toLowerCase();
}
public static void main(String[] args) {
byte[] data = new byte[] {0x2d, (byte)0xee, 0x63, 0x5e, 0x78, 0x01, 0x00, 0x00};
long time = bytes2Long(data);
System.out.println("long->"+time);
byte[] res = long2Bytes(time);
for(int i=0;i<8;i++) {
System.out.print(num2Hex(res[i])+" ");
}
System.out.println();
byte[] darr = new byte[] {0x00, (byte)0xd0,(byte)0xe2, 0x3e, (byte)0xe6, (byte)0x85, 0x77, 0x42 };
double d2 = bytes2Double(darr);
System.out.println("double->"+d2);
byte[] res2 = double2Bytes(d2);
for(int i=0;i<8;i++) {
System.out.print(num2Hex(res2[i])+" ");
}
System.out.println();
}
}
这段代码运行的结果如下所示:
long->1616491310637
2d ee 63 5e 78 01 00 00
double->1.616491310637E12
00 d0 e2 3e e6 85 77 42
这里重点我们看转换的代码,无论是数字转字节,还是字节转数字,在java里面,double与long类型有着非常密切的关系:
这里唯一不一样的地方在于最后一步返回的时候,一个直接返回,一个需要通过Double.longBitsToDouble()转换一下。
接着看看字节转数字:
double类型,在转byte[]之前,需要通过一个Double.doubleToRawLongBits()转为long类型,然后进行按位取值。
重点
我的问题是什么呢,就是我与嵌入式终端约定是解析double类型的字节码,但是他们给我传过来的是longlong类型的整数,java端按照double来解析,发现走到这一步:
实际上,经过最后一步Double.longBitsToDouble()之后,结果就发生了翻天覆地的变化了,我又懵逼了,不知道如何解释,只能靠猜,终端是不是搞错了,明明是double的,最后传过来的却是longlong,我经过各种确认,最后发现他们确实搞错了。
不过,从这件事情里面我感觉java里面的double应该和long有着很紧密的联系。
我们再来看看long与double转换字节之后的结果:
字面值和类型转换,他们确实有些关系,但是他们的取值范围却相差很大。
long -> 2^63-1 = 9223372036854775807 (19位) 最大值
double ->2^1024-1 = 1.7976931348623157E308(309位) 最大值
有意思的是,double最大值只能通过Double.MAX_VALUE表示,我想着你既然是2^1024-1,那肯定也可以通过其他的数学表达式表示出来,结果Math.power(2,1024)-1直接显示Infinity。
那就按照推导的方式:2^1024-1 = 2^1023^2 -1 = 2^1023+2^1023-1还是不行,看来要利用2^1023+2^1022+2^1021+2^1020+...-1的方式了。
我最后用试的办法终于试出来了:
double m = 0;
for(int i = 971;i<1024;i++) {
m+=Math.pow(2, i);
}
令我比较惊讶的是,字面上的结果已经就是 1.7976931348623157E308 了,我以为是个近似值,结果和Double.MAX_VALUE一比较,嗨,竟然相等:
package com.xxx.huali.hualitest;
public class LongValueTest {
public static void main(String[] args) {
long l = Long.MAX_VALUE;
long l2 = l-1;
System.out.println("max(long):"+l);
System.out.println(l==l2);
double d = Double.MAX_VALUE;
double e = d-1;
System.out.println("max(double):"+d);
System.out.println(d==e);
System.out.println(Double.MAX_EXPONENT);
double m = 0;
for(int i = 971;i<1024;i++) {
m+=Math.pow(2, i);
}
System.out.println("max(double):"+m);
System.out.println(m==Double.MAX_VALUE);
}
}
打印结果:
max(long):9223372036854775807
false
max(double):1.7976931348623157E308
true
1023
max(double):1.7976931348623157E308
true
有些意外,理论上2^1024-1 肯定不会只是 2^971+2^972+...+2^1023,计算机里的事情,谁说的清呢。
另一个有意思的事情是:long类型的整数,最大值-1 != 最大值 ,这个好理解,但是double类型 最大值-1 == 最大值,这个好奇葩啊。