最近在解决线上的bug时,遇到一个问题。
第三方传过来的课程编码时4214410000,然而我们存进数据库的值却变成了4214409980。查遍了所有的代码都查不到有对这个值修改的代码。最后,通过打印日志的方法,找到了这个值改变的代码段。最终确定是Float.parseFloat(“4214410000”)改变了这个值。
老大告诉我这还不算解决问题,要查查为什么有的课程代码改变了,有的却没有改变,例如4211030020
。
然后,我就研究了jdk中Float.parseFloat()的源码。
在jdk 1.8中 Float.parseFloat()的实现是在FloatingDecimal.cass文件中,其中主要有两个方法,一个是readJavaFormatString()方法,一个是ASCIIToBinaryBuffer()。
第一个方法有以下几个功能
1.检验是否是空字符串
2.字符串是否是以'N' ,'I'开头,或者是否是八进制
3.给下一个方法提供四个参数,是否是负数,总长度,字符串数组,(总长度-字符串最后连续0的个数)
ASCIIToBinaryBuffer()获得以上四个参数后就开始生成float。
在看源码的时候,我发现4214410000生成的float步骤和4211030020不一致,导致不一致的原因是4214410000后面是连续的5个0。所以4214410000生成的float是421441.0* 10000.0得到的结果就是4.21440998E9。结果不是我们预想的整数,这是因为float的特性所决定的。《Effective Java》中已经讲出了这种问题,float/double不能停供完全精确的计算结果。这个原理其实很简单,float/int都是32bit(也就是一共有232个精确值),而int的范围是-231 ~ 231-1,而Float的最大值是3.4028235e+38,远大于231 - 1。而且,int只负责个数有限的整数,而浮点却要用来表示个数无穷的小数,显然力不从心。浮点精确值可以简单视作一个以0为中心的正态分布,绝对值越小(越接近0的地方),相邻两个精确值月密集。比如,最近的两个值可能只相差0.00000...几十个0...01,而最远的两个精确值,却差了2.028241E31。
到此,问题解决。以下贴上我的测试Float.parseFloat()的代码。
Float.parseFloat()的实现代码是我从jdk源码拷过来的,使用的是断点调试的方法。
public class Test {
static float readJavaFormatString(String var0) throws NumberFormatException {
boolean var1 = false;
boolean var2 = false;
try {
var0 = var0.trim();
int var5 = var0.length();
if (var5 == 0) {
throw new NumberFormatException("empty String");
}
int var6 = 0;
switch(var0.charAt(var6)) {
case '-':
var1 = true;
case '+':
++var6;
var2 = true;
}
char var4 = var0.charAt(var6);
char[] var21 = new char[var5];
int var8 = 0;
boolean var9 = false;
int var10 = 0;
int var11 = 0;
int var12;
for(var12 = 0; var6 < var5; ++var6) {
var4 = var0.charAt(var6);
if (var4 == '0') {
++var11;
} else {
if (var4 != '.') {
break;
}
if (var9) {
throw new NumberFormatException("multiple points");
}
var10 = var6;
if (var2) {
var10 = var6 - 1;
}
var9 = true;
}
}
for(; var6 < var5; ++var6) {
var4 = var0.charAt(var6);
if (var4 >= '1' && var4 <= '9') {
var21[var8++] = var4;
var12 = 0;
} else if (var4 == '0') {
var21[var8++] = var4;
++var12;
} else {
if (var4 != '.') {
break;
}
if (var9) {
throw new NumberFormatException("multiple points");
}
var10 = var6;
if (var2) {
var10 = var6 - 1;
}
var9 = true;
}
}
var8 -= var12;
boolean var13 = var8 == 0;
if (!var13 || var11 != 0) {
int var3;
if (var9) {
var3 = var10 - var11;
} else {
var3 = var8 + var12;
}
if (var6 < var5 && ((var4 = var0.charAt(var6)) == 'e' || var4 == 'E')) {
byte var14 = 1;
int var15 = 0;
int var16 = 214748364;
boolean var17 = false;
++var6;
int var18;
switch(var0.charAt(var6)) {
case '-':
var14 = -1;
case '+':
++var6;
default:
var18 = var6;
}
while(var6 < var5) {
if (var15 >= var16) {
var17 = true;
}
var4 = var0.charAt(var6++);
if (var4 < '0' || var4 > '9') {
--var6;
break;
}
var15 = var15 * 10 + (var4 - 48);
}
int var19 = 324 + var8 + var12;
if (!var17 && var15 <= var19) {
var3 += var14 * var15;
} else {
var3 = var14 * var19;
}
if (var6 == var18) {
throw new NumberFormatException("For input string: "" + var0 + """);
}
}
if (var6 >= var5 || var6 == var5 - 1 && (var0.charAt(var6) == 'f' || var0.charAt(var6) == 'F' || var0.charAt(var6) == 'd' || var0.charAt(var6) == 'D')) {
if (var13) {
return var1 ? -0.0f : 0.0f;
}
return floatValue(var1,var3,var21,var8);
}
}
}catch (Exception e){
e.printStackTrace();
}
throw new NumberFormatException("For input string: "" + var0 + """);
}
private static final int SINGLE_MAX_SMALL_TEN;
private static final double[] SMALL_10_POW = new double[]{1.0D, 10.0D, 100.0D, 1000.0D, 10000.0D, 100000.0D, 1000000.0D, 1.0E7D, 1.0E8D, 1.0E9D, 1.0E10D, 1.0E11D, 1.0E12D, 1.0E13D, 1.0E14D, 1.0E15D, 1.0E16D, 1.0E17D, 1.0E18D, 1.0E19D, 1.0E20D, 1.0E21D, 1.0E22D};
private static final float[] SINGLE_SMALL_10_POW = new float[]{1.0F, 10.0F, 100.0F, 1000.0F, 10000.0F, 100000.0F, 1000000.0F, 1.0E7F, 1.0E8F, 1.0E9F, 1.0E10F};
private static final int MAX_SMALL_TEN;
private static final double[] BIG_10_POW = new double[]{1.0E16D, 1.0E32D, 1.0E64D, 1.0E128D, 1.0E256D};
private static final double[] TINY_10_POW = new double[]{1.0E-16D, 1.0E-32D, 1.0E-64D, 1.0E-128D, 1.0E-256D};
static {
MAX_SMALL_TEN = SMALL_10_POW.length - 1;
SINGLE_MAX_SMALL_TEN = SINGLE_SMALL_10_POW.length - 1;
}
static float floatValue(Boolean isNegative,int decExponent,char[] digits,int nDigits ) {
int var1 = Math.min(nDigits, 8);
int var2 = digits[0] - 48;
for(int var3 = 1; var3 < var1; ++var3) {
var2 = var2 * 10 + digits[var3] - 48;
}
float var27 = (float)var2;
int var4 = decExponent - var1;
int var7;
if (nDigits <= 7) {
if (var4 == 0 || var27 == 0.0F) {
return isNegative ? -var27 : var27;
}
if (var4 >= 0) {
if (var4 <= SINGLE_MAX_SMALL_TEN) {
var27 *= SINGLE_SMALL_10_POW[var4];
return isNegative ? -var27 : var27;
}
int var5 = 7 - var1;
if (var4 <= SINGLE_MAX_SMALL_TEN + var5) {
var27 *= SINGLE_SMALL_10_POW[var5];
var27 *= SINGLE_SMALL_10_POW[var4 - var5];
return isNegative ? -var27 : var27;
}
} else if (var4 >= -SINGLE_MAX_SMALL_TEN) {
var27 /= SINGLE_SMALL_10_POW[-var4];
return isNegative ? -var27 : var27;
}
} else if (decExponent >= nDigits && nDigits + decExponent <= 15) {
long var29 = (long)var2;
for(var7 = var1; var7 < nDigits; ++var7) {
var29 = var29 * 10L + (long)(digits[var7] - 48);
}
double var31 = (double)var29;
var4 = decExponent - nDigits;
var31 *= SMALL_10_POW[var4];
var27 = (float)var31;
return isNegative ? -var27 : var27;
}
double var28 = (double)var27;
if (var4 > 0) {
if (decExponent > 39) {
return isNegative ? -1.0F: 1.0F ;
}
if ((var4 & 15) != 0) {
var28 *= SMALL_10_POW[var4 & 15];
}
if ((var4 >>= 4) != 0) {
for(var7 = 0; var4 > 0; var4 >>= 1) {
if ((var4 & 1) != 0) {
var28 *= BIG_10_POW[var7];
}
++var7;
}
}
} else if (var4 < 0) {
var4 = -var4;
if (decExponent < -46) {
return isNegative ? -0.0F : 0.0F;
}
if ((var4 & 15) != 0) {
var28 /= SMALL_10_POW[var4 & 15];
}
if ((var4 >>= 4) != 0) {
for(var7 = 0; var4 > 0; var4 >>= 1) {
if ((var4 & 1) != 0) {
var28 *= TINY_10_POW[var7];
}
++var7;
}
}
}
var27 = Math.max(1.4E-45F, Math.min(3.4028235E38F, (float)var28));
if (nDigits > 200) {
nDigits = 201;
digits[200] = '1';
}
FDBigInteger var30 = new FDBigInteger((long)var2,digits, var1,nDigits);
var4 = decExponent - nDigits;
int var8 = Float.floatToRawIntBits(var27);
int var9 = Math.max(0, -var4);
int var10 = Math.max(0, var4);
var30 = var30.multByPow52(var10, 0);
var30.makeImmutable();
FDBigInteger var11 = null;
int var12 = 0;
do {
int var13 = var8 >>> 23;
int var14 = var8 & 8388607;
int var15;
int var16;
if (var13 > 0) {
var14 |= 8388608;
} else {
assert var14 != 0 : var14;
var15 = Integer.numberOfLeadingZeros(var14);
var16 = var15 - 8;
var14 <<= var16;
var13 = 1 - var16;
}
var13 -= 127;
var15 = Integer.numberOfTrailingZeros(var14);
var14 >>>= var15;
var16 = var13 - 23 + var15;
int var17 = 24 - var15;
int var18 = var9;
int var19 = var10;
if (var16 >= 0) {
var18 = var9 + var16;
} else {
var19 = var10 - var16;
}
int var20 = var18;
int var21;
if (var13 <= -127) {
var21 = var13 + var15 + 127;
} else {
var21 = 1 + var15;
}
var18 += var21;
var19 += var21;
int var22 = Math.min(var18, Math.min(var19, var20));
var18 -= var22;
var19 -= var22;
var20 -= var22;
FDBigInteger var23 = FDBigInteger.valueOfMulPow52((long)var14, var9, var18);
if (var11 == null || var12 != var19) {
var11 = var30.leftShift(var19);
var12 = var19;
}
FDBigInteger var24;
int var25;
boolean var26;
if ((var25 = var23.cmp(var11)) > 0) {
var26 = true;
var24 = var23.leftInplaceSub(var11);
if (var17 == 1 && var16 > -126) {
--var20;
if (var20 < 0) {
var20 = 0;
var24 = var24.leftShift(1);
}
}
} else {
if (var25 >= 0) {
break;
}
var26 = false;
var24 = var11.rightInplaceSub(var23);
}
var25 = var24.cmpPow52(var9, var20);
if (var25 < 0) {
break;
}
if (var25 == 0) {
if ((var8 & 1) != 0) {
var8 += var26 ? -1 : 1;
}
break;
}
var8 += var26 ? -1 : 1;
} while(var8 != 0 && var8 != 2139095040);
if (isNegative) {
var8 |= -2147483648;
}
return Float.intBitsToFloat(var8);
}
public static void main(String args[]) {
String ss = "4214410000";
String cc = "4211030020";
readJavaFormatString(ss);
readJavaFormatString(cc);
}
}