Parsing 如何从字符串中手动解析浮点数

Parsing 如何从字符串中手动解析浮点数,parsing,floating-point,precision,Parsing,Floating Point,Precision,当然,大多数语言都有用于此目的的库函数,但假设我想自己做 假设浮点像在C或Java程序中一样给出(除了'f'或'd'后缀),例如“4.2e1”、“.42e2”或简单地“42”。一般来说,小数点前有“整数部分”,小数点后有“小数部分”,还有“指数”。这三个都是整数 查找和处理单个数字很容易,但是如何将它们组合成float或double类型的值而不丢失精度? 我想把整数部分乘以10^n,其中n是小数部分的位数,然后把小数部分加到整数部分,然后从指数中减去n。例如,这有效地将4.2e1转换为42e0。

当然,大多数语言都有用于此目的的库函数,但假设我想自己做

假设浮点像在C或Java程序中一样给出(除了'f'或'd'后缀),例如“
4.2e1
”、“
.42e2
”或简单地“
42
”。一般来说,小数点前有“整数部分”,小数点后有“小数部分”,还有“指数”。这三个都是整数

查找和处理单个数字很容易,但是如何将它们组合成
float
double
类型的值而不丢失精度?

我想把整数部分乘以10^n,其中n是小数部分的位数,然后把小数部分加到整数部分,然后从指数中减去n。例如,这有效地将
4.2e1
转换为
42e0
。然后我可以使用
pow
函数来计算10^指数,并将结果与新的整数部分相乘。问题是,这种方法是否保证了整个过程中的最大精度


对此有何想法?

使用状态机。这相当容易做到,即使在数据流中断时也能工作(您只需保持状态和部分结果)。您还可以使用解析器生成器(如果您正在执行更复杂的操作)。

为此,您必须了解标准IEEE 754,以获得正确的二进制表示。之后,您可以使用Float.intBitsToFloatDouble.longBitsToDouble


如果希望得到尽可能精确的结果,应使用更高的内部工作精度,然后将结果下变频到所需的精度。如果您不介意一些ULP的错误,那么您可以根据需要以所需的精度重复乘以10。我将避免使用pow()函数,因为它将为大指数生成不精确的结果。

我将使用其二进制表示直接组装浮点数

一个接一个地读入数字,首先找出所有数字。用整数算术来做。也要记录小数点和指数。这一点以后会很重要

现在你可以组装你的浮点数了。要做的第一件事是扫描第一组数字的整数表示(从高到低)

紧跟在第一位之后的位是尾数

获得指数也不难。从科学记数法中可以知道第一个一位的位置、小数点的位置和可选指数。将它们组合起来,加上浮点指数偏差(我认为是127,但请检查一些参考资料)

这个指数应该在0到255之间。如果它是大的或小的,你有一个正的或负的无限数(特例)

将指数存储在浮点数的24到30位

最重要的位就是符号。一表示负,零表示正

它比实际情况更难描述,试着分解一个浮点数,看看指数和尾数,你会发现这是多么容易


顺便说一句,在浮点运算本身是一个坏主意,因为你总是会强迫你的尾数被截断为23个有效位。这样就无法得到精确的表示。

解析时可以忽略小数点(除了它的位置)。假设输入是: 156.7834e10。。。这可以很容易地解析为整数1567834,后跟e10,然后将其修改为e6,因为小数点是浮点“数字”部分末尾的4位数字

精确性是一个问题。您需要检查所使用语言的IEEE规范。如果尾数(或分数)中的位数大于整数类型中的位数,则当有人键入以下数字时,您可能会失去精度:

5123.123123e0-在我们的方法中转换为5123123123,它不适合整数,但5.123123123的位可能适合浮点规范的尾数

当然,您可以使用一种方法,将小数点前面的每个数字乘以当前总数(以浮点数形式)10,然后添加新的数字。对于小数点后的数字,将该数字乘以10的增长幂,然后再加上当前总数。然而,这个方法似乎在问为什么要这样做,因为它需要使用浮点原语而不使用现成的解析库

/* use this to start your atof implementation */

/* atoi - christopher.watford@gmail.com */
/* PUBLIC DOMAIN */
long atoi(const char *value) {
  unsigned long ival = 0, c, n = 1, i = 0, oval;
  for( ; c = value[i]; ++i) /* chomp leading spaces */
    if(!isspace(c)) break;
  if(c == '-' || c == '+') { /* chomp sign */
    n = (c != '-' ? n : -1);
    i++;
  }
  while(c = value[i++]) { /* parse number */
    if(!isdigit(c)) return 0;
    ival = (ival * 10) + (c - '0'); /* mult/accum */
    if((n > 0 && ival > LONG_MAX)
    || (n < 0 && ival > (LONG_MAX + 1UL))) {
      /* report overflow/underflow */
      errno = ERANGE;
      return (n > 0 ? LONG_MAX : LONG_MIN);
    }
  }
  return (n>0 ? (long)ival : -(long)ival);
}

不管怎样,祝你好运

在不损失精度的情况下,无法将表示数字的任意字符串转换为double或float。有许多分数可以精确地用十进制表示(例如“0.1”),只能用二进制浮点或双精度近似表示。这类似于分数1/3不能用十进制精确表示,你只能写0.333333


如果您不想直接使用库函数,为什么不看看这些库函数的源代码呢?你提到Java;大多数JDK都附带类库的源代码,因此您可以查看java.lang.Double.parseDouble(String)方法的工作原理。当然,像BigDecimal这样的东西更适合控制精度和舍入模式,但您说过它需要是浮点或双精度。

所有其他答案都忽略了正确实现这一点的难度。在这种情况下,您可以使用第一次切割方法,这种方法在一定程度上是准确的,但除非您考虑IEEE舍入模式(等),否则您永远不会得到正确的答案。我以前写过一些简单的实现,但有很多错误

如果你不害怕数学,我
if(biasedExponent >= 0)
    return integerMantissa * (10^biasedExponent);
else
    return integerMantissa / (10^(-biasedExponent));
10^8 > 2^24 > 10^7
5^11 > 2^24 > 5^10
10^16 > 2^53 > 10^15
5^23 > 2^53 > 5^22