0

我们使用 FPU 解决汇编语言中的实数问题。通常我们使用C语言或准备好的函数来编写输入输出代码。例如:

    ; Receiving input and output descriptors for the console
    invoke  GetStdHandle,   STD_INPUT_HANDLE
    mov     hConsoleInput,  eax

    invoke  GetStdHandle,   STD_OUTPUT_HANDLE
    mov     hConsoleOutput, eax

    invoke  ClearScreen
    ;input X
    invoke  WriteConsole, hConsoleOutput, ADDR aszPromptX,\
            LENGTHOF aszPromptX - 1, ADDR BufLen, NULL
    invoke  ReadConsole, hConsoleInput, ADDR Buffer,\
            LENGTHOF Buffer, ADDR BufLen, NULL
    finit
    invoke  StrToFloat, ADDR Buffer, ADDR X

如何在不使用现成函数的情况下用汇编语言输入和输出实数?

4

1 回答 1

4

这与如何实现这些功能/它们如何在幕后工作实际上是同一个问题。我只想谈谈这个答案中的输入;我不确定哪些算法适用于 float->string。

操作系统提供的功能让您可以一次或按块读取/写入(打印)字符。问题中有趣的 / FP-specific 部分只是 float->string 和 string->float 部分。其他一切都与读取/打印整数相同(模调用约定差异:浮点数通常在 FP 寄存器中返回)。


如果您希望结果始终正确四舍五入到最接近的可表示 FP 值,则正确实现strtod(字符串到双精度)和单精度等价物非常重要,特别是如果您希望它也高效,并且可以正确处理输入直到double可以容纳的最大有限值的极限。

一旦您了解了算法的详细信息(在查看单个数字和执行 FP 乘法/除法/加法,或对 FP 位模式的整数运算),您就可以在 asm 中为您喜欢的任何平台实现它。出于某种原因,您在示例中使用了 x87finit指令。

有关glibc 实现的详细信息,请参见http://www.exploringbinary.com/how-glibc-strtod-works/ ,以及http://www.exploringbinary.com/how-strtod-works-and-sometimes-doesnt/对于另一个广泛使用的实现。

概述第一篇文章,glibcstrtod使用扩展精度整数运算。它解析输入的十进制字符串以确定整数部分和小数部分。例如456.833e2(科学记数法)有 的整数部分45683和小数部分0.3

它将两个部分分别转换为浮点数。整数部分很简单,因为已经有硬件支持将整数转换为浮点数。例如 x87fild或 SSE2 cvtsi2sd,或其他架构上的其他任何东西。但是如果整数部分大于最大64位整数,就没有那么简单了,需要将BigInteger转换为float/double,硬件不支持。

请注意,FLT_MAXIEEE binary32 的偶数(单精度)float(2 − 2^−23) × 2^127略低于2^128,因此您可以对 string-> 使用 128 位整数float,如果换行,则正确的float结果是+Infinity. FLT_MAX 位模式是0x7f7fffff:尾数全一 = 1.999... 最大指数。在十进制中,它是~3.4 × 10^38.

但是,如果您不关心效率,我认为您可以将每个数字转换为 a float(或索引一个已转换值的数组float),并执行通常的total = total*10 + digit, 或在这种情况下total = total*10.0 + digit_values[digit]。FP mul / add 精确到整数,直到两个相邻的可表示值相距大于 1.0(即何时nextafter(total, +Infinity)total+2.0),即当 1 ulp 大于 时1.0

实际上,要获得正确的舍入,您需要先添加小值否则它们各自单独向下舍入,当它们加在一起时,它们可能会将大值增加到下一个可表示的值。

因此,如果您仔细操作,您可能可以使用 FPU,例如以 8 位数字块工作并按 10^8 缩放或其他东西,并从最小的开始添加。您可以将每个 8 位数字字符串转换为整数并使用 hardware int-> float


小数部分甚至更棘手,特别是如果你想避免重复除以 10 来获得位置值,你应该避免这种情况,因为它很慢并且因为1/10不能完全用二进制浮点表示,所以如果你的所有位置值都会有舍入错误您以“明显”的方式进行操作。

但如果整数部分非常大,则所有 53 个尾数位double可能已经由整数部分确定。所以 glibc 检查,并且只进行大整数除法以从小数部分获得它需要的位数(如果有的话)。

无论如何,我强烈建议阅读这两篇文章。


顺便说一句,如果您不熟悉 IEEE754 binary64(又名)用来表示数字的位模式,请参阅https://en.wikipedia.org/wiki/Double-precision_floating-point_format 。double您不需要编写简单的实现,但它确实有助于理解浮动。而对于 x86 SSE,您需要知道符号位在哪里来实现绝对值 (ANDPS) 或否定 (XORPS)。 使用 SSE 计算绝对值的最快方法absor没有特殊说明neg,您只需使用布尔操作来操作符号位。(比从零减去更有效。)


如果您不关心精确到最后一个 ULP(最后一个单位 = 尾数的最低位),那么您可以做一个更简单的算法,乘以 10 并为字符串 -> 整数加上 like,然后缩放最后是 10 的幂。

但是一个健壮的库函数不能做到这一点,因为创建一个比最终结果大很多倍的临时值意味着对于可以表示范围内的某些输入,它将溢出(到+/- Infinitydouble) 。或者+/- 0.0如果您创建较小的临时值,可能会下溢。

分别处理整数和小数部分可以避免溢出问题。

有关可能会溢出的非常简单的乘法/加法方法的示例,请参见codereview.SE 上的此 C 实现。我只是快速浏览了一下,但我没有看到它拆分整数/小数部分。它只处理科学计数法E99或最后的任何东西,重复乘以或除以 10。

于 2017-11-28T17:13:03.727 回答