如何在没有协处理器的情况下创建浮点功能

发布于 2025-02-05 19:52:27 字数 1606 浏览 2 评论 0原文

我一直在尝试为Motorola 68000(原始的,没有浮点协处理器)创建一个可以转换A binary32浮点值可以在屏幕上显示的文本。我正在使用此网站将浮点转换为hex以获得逻辑的细分。这是我在该网站上具有测试值0x3e400000的输出:

0x3e400000
  3      e       4       0      0       0       0        0
0011   1110    0100    0000    0000    0000    0000    0000
0   01111100    10000000000000000000000
sign    exponent    mantissa
+1  124 1.10000000000000000000000 (binary)
+1 *    2^(124 - 127) * 1.5
+1 *    0.125000000 *   1.5

事实是,这一切似乎都是循环逻辑。没有浮点协处理器,您将如何计算和存储2^-3或1.5?这些都不需要与输入相同的例程运行,等等?

如果有帮助的话,这就是我在装配代码中所拥有的。如果有您不认识的说明,我创建了宏来帮助我,并且在评论中对它们进行了解释。也许这会使我想做的事情变得更加清晰。

Float2Text:
    ;input: d0
    ;clobbers d1
    pushRegs d2-d7  ;MOVEM.L D2-D7,-(SP)
    MOVEQ.L #0,D7
    MOVEQ.L #0,D6
    MOVEQ.L #0,D5
    MOVEQ.L #0,D4
    
    
    CLX             ;ANDI #%00001111,CCR
    MOVE.L D0,D2
    ROXL.L D0
    ROXL.L D7       ;TRANSFER SIGN BIT INTO D7
    
    SWAP D0
    ROR.W #8,D0     ;GET EXPONENT INTO BOTTOM BYTE
    CMP.B #0,D0
    BEQ .isZero
    CMP.B #%11111111,D0
    BEQ .isInfinity
    
    SUB.B #127,D0
    ;THIS IS THE UNBIASED EXPONENT OF 2
    
    ;NOW WE HAVE THE EXPONENT IN D0, AND THE SIGN BIT IN D7
    ;WE NEED TO SUM UP THE MANTISSA BITS.
    MOVE.B D0,D3
    MOVE.L D2,D0
    AND.L #%00000000011111111111111111111111,D0     ;CLEAR SIGN AND EXPONENT
    
    
    
.isZero:

.isInfinity:

.done
    popRegs d2-d7   ;MOVEM.L (SP)+,D2-D7
    RTS

I've been trying to create a library for the Motorola 68000 (the original, without a floating point coprocessor) that can convert a binary32 floating point value to text that can be displayed on-screen. I was using this website which converts floats to hex to get a breakdown of the logic. Here's my output on that website with the test value 0x3E400000:

0x3e400000
  3      e       4       0      0       0       0        0
0011   1110    0100    0000    0000    0000    0000    0000
0   01111100    10000000000000000000000
sign    exponent    mantissa
+1  124 1.10000000000000000000000 (binary)
+1 *    2^(124 - 127) * 1.5
+1 *    0.125000000 *   1.5

The thing is, this all seems like circular logic. Without a floating point coprocessor how would you calculate and store 2^-3 or 1.5? Wouldn't each of those need to be run through the same routine as your input, and so on?

This is what I have in my assembly code if it helps. If there are instructions you don't recognize, I've created macros to help me out and I've explained them in the comments. Maybe this will make what I'm trying to do clearer.

Float2Text:
    ;input: d0
    ;clobbers d1
    pushRegs d2-d7  ;MOVEM.L D2-D7,-(SP)
    MOVEQ.L #0,D7
    MOVEQ.L #0,D6
    MOVEQ.L #0,D5
    MOVEQ.L #0,D4
    
    
    CLX             ;ANDI #%00001111,CCR
    MOVE.L D0,D2
    ROXL.L D0
    ROXL.L D7       ;TRANSFER SIGN BIT INTO D7
    
    SWAP D0
    ROR.W #8,D0     ;GET EXPONENT INTO BOTTOM BYTE
    CMP.B #0,D0
    BEQ .isZero
    CMP.B #%11111111,D0
    BEQ .isInfinity
    
    SUB.B #127,D0
    ;THIS IS THE UNBIASED EXPONENT OF 2
    
    ;NOW WE HAVE THE EXPONENT IN D0, AND THE SIGN BIT IN D7
    ;WE NEED TO SUM UP THE MANTISSA BITS.
    MOVE.B D0,D3
    MOVE.L D2,D0
    AND.L #%00000000011111111111111111111111,D0     ;CLEAR SIGN AND EXPONENT
    
    
    
.isZero:

.isInfinity:

.done
    popRegs d2-d7   ;MOVEM.L (SP)+,D2-D7
    RTS

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

海风掠过北极光 2025-02-12 19:52:28

对于float2Text

浮点数更准确地描述为“基本2浮点”。例如,二进制中的数字为1.25表示为1.01b或101b>> 2。

您的最初目标应该是将“基本2浮点”转换为“基本10浮点”。例如,对于“基本10浮点”,数字1.25将是数字(字符?)125,其指数为-2,例如“ 125 * 10^(-2)”。一旦您拥有“基本10浮点”中的数字,它就变得相对容易打印(例如,在正确的位置插入小数点时只打印数字)。

首先将“基本2浮点”编号分为签名的整数“底座2 Mantissa”(同时照顾“隐含的前导1”(如果不是次级正常))和一个签名的整数“基本2指数”(在服用时照顾偏见)。

下一个;如果“基本2指数”为负,则将原始数量乘以10,同时减少“基本10指数”以保持跟踪。请注意,您可以通过将“基本2 Mantissa”乘以5并增加“基本2指数”来乘以10。以相同的方式,您可以通过将“基本2 Mantissa”乘以25并将2添加到“基本2指数”来乘以100;或通过将“基本2 Mantissa”乘以125并将3添加到“基本2指数”等来乘以1000。例如,也许模糊地喜欢:

    while(base2_exponent < 0) {
        switch(base2_exponent) {
            case -1: 
                base2_mantissa *= 5; base2_exponent -= 1;
                base10_exponent += 1;
                break;
            case -2: 
                base2_mantissa *= 25; base2_exponent -= 2;
                base10_exponent += 2;
                break;
            case -3: 
                base2_mantissa *= 125; base2_exponent -= 3;
                base10_exponent += 3;
                break;
            default: 
                base2_mantissa *= 625; base2_exponent -= 4;
                base10_exponent += 4;
                break;
            }
        }

这会导致一个小问题 - 对于最坏的情况(数字非常小); “基本2 Mantissa”可以生长到大约160位。为了解决此问题,您必须使用“大整数”(并且您不能仅仅使用base2_mantissa *= 625;使用一个普通整数,并且需要更像big_integer_multiply(&amp; base2_mantissa) ,625);)。

下一个;如果“基本2指数”为正;将“基本2 Mantissa”乘以2,然后减少“基本2指数”(或者换句话说,将“基本2 Mantissa”移位为“基本2指数”)。这也有类似的问题 - 对于最坏的情况(非常大的情况),“基本2曼氏”可以生长到大约160位。

在这一点上,“基本2指数”将为零,可以忽略。

下一步是将“ Base 2 Mantissa”转换为“ 10 Mantissa”。从理论上讲,这不应该那么困难 - 也许像一样模糊地点(base2_mantissa&gt; 0){ *dest = base2_mantissa%10 +'0'; base2_mantissa /= 10; dest-; }除非您将使用“大整数”(带有160位整数)进行Modulo和Division。然而;最好从最重要的数字到最不重要的数字工作。为此,请有一个10(例如1,10,100,1000,10000,....)的功率表查找表(base2_mantissa&gt; 0){divisor = find_largest_divisor_from_table(base2_mantissa); *dest = base2_mantissa / divisor +'0'; base2_mantissa%=除数; dest ++; } 。这使您可以尽早退出 - 例如,如果您只需要10个有效数字的最大数字,则可以使用10 char> char的固定尺寸缓冲区,并在拥有10位数字时停止。当然,所有这些(除数表等)也需要使用“大整数”。

最后一步是打印“基本10 Mantissa”,同时确保您插入小数点字符('。'),其中“基本10指数”应该是。如果您在上一个步骤中使用了“最重要的数字达到最不重要的数字”,则可以将其与上一步结合在一起(例如一次打印一个数字,而无需存储“ base 10 Mastissa”,同时将小数点插入小数点正确的位置)。

对于其他浮点操作:

否定是微不足道的(标志位的XOR)。

对于其他所有内容,以3个阶段进行操作 - 将源值分为“签名的整数Mantissa”和“签名的整数指数”(主要如上所述);做一个取决于操作的“中间阶段”;然后将结果“签名的整数Mantissa”和“签名的整数指数”组合回正确的格式。

作为一般指南(没有并发症 - 溢出,下流,圆形,nans,...)“中间阶段”为:

  • 用于乘法:result_mantissa = src1_mantissa * src2_mantissa; result_exponent = src1_exponent + src2_exponent -mantissa_size;

  • 用于分区:result_mantissa =(src1_mantissa&lt;&lt; mantissa_size) / src2_mantissa; result_exponent = src1_exponent -src2_exponent;

  • 加法:找到具有最积极指数的值;将另一个值的Mantissa右转,同时增加其指数直到指数匹配; result_mantissa = src1_mantissa + src2_mantissa; result_exponent = src1_exponent;

  • 用于减法:否定第二个值并使用添加代替

  • 左移左右:如果mantissa太小(次正常)移动它尽可能多地向左移动(同时确保它不会变得太大);然后将剩余的移位计数添加到指数。

  • 右移动:在使指数变得“太负”的同时,尽可能多地从指数中减去移位计数。如果剩下任何偏移计数,请向右移动mantissa(形成子正常)。

请注意,将结果“签名的整数Mantissa”和“签名的整数指数”结合到正确的格式应包括某种,而(result_mantissa的幅度太大){result_mantissa&gt;&gt;&gt;&gt; 1; result_exponent ++); };最终有点混乱(可以/应该考虑到舍入模式)。

For Float2Text:

A floating point number is more accurately described as "base 2 floating point". E.g. the number 1.25 in binary is represented as 1.01b or 101b >> 2.

Your initial goal should be to convert "base 2 floating point" into "base 10 floating point". E.g. for "base 10 floating point" the number 1.25 will be the digits (characters?) 125 with the exponent of -2, like "125 * 10^(-2)". Once you have the number in "base 10 floating point" it becomes relatively easy to print (e.g. just print the digits while inserting the decimal point in the right place).

Start by splitting the "base 2 floating point" number into signed integer "base 2 mantissa" (while taking care of the "implied leading 1" if it's not a sub-normal) and a signed integer "base 2 exponent" (while taking care of the bias).

Next; if the "base 2 exponent" is negative, multiply the original number by 10 while decreasing the "base 10 exponent" to keep track. Note that you can multiply by 10 by multiplying "base 2 mantissa" by 5 and increasing "base 2 exponent". In the same way you can multiply by 100 by multiplying "base 2 mantissa" by 25 and adding 2 to "base 2 exponent"; or multiply by 1000 by multiplying "base 2 mantissa" by 125 and adding 3 to "base 2 exponent", etc. You can speed this up a lot by using the existing "base 2 exponent" to select the right multiplier; e.g. maybe something vaguely like:

    while(base2_exponent < 0) {
        switch(base2_exponent) {
            case -1: 
                base2_mantissa *= 5; base2_exponent -= 1;
                base10_exponent += 1;
                break;
            case -2: 
                base2_mantissa *= 25; base2_exponent -= 2;
                base10_exponent += 2;
                break;
            case -3: 
                base2_mantissa *= 125; base2_exponent -= 3;
                base10_exponent += 3;
                break;
            default: 
                base2_mantissa *= 625; base2_exponent -= 4;
                base10_exponent += 4;
                break;
            }
        }

This will cause a minor problem - for worst case (very small numbers); the "base 2 mantissa" can grow to become about 160 bits. To deal with this you have to use "big integers" (and you can't just do base2_mantissa *= 625; with a normal integer and will need something more like big_integer_multiply(&base2_mantissa, 625);).

Next; if the "base 2 exponent" is positive; multiply "base 2 mantissa" by 2 and decrease "base 2 exponent" (or in other words, shift "base 2 mantissa" left by the "base 2 exponent"). This has a similar problem - for worst case (very large numbers) the "base 2 mantissa" can grow to become about 160 bits.

At this point, "base 2 exponent" will be zero and can be ignored.

The next step is converting "base 2 mantissa" into a "base 10 mantissa". In theory this shouldn't be that hard - maybe something vaguely like while(base2_mantissa > 0) { *dest = base2_mantissa % 10 + '0'; base2_mantissa /= 10; dest--; } except that you'll be using "big integer" (with 160 bit integers) for modulo and division. However; it's better to work from most significant digit to least significant digit. To do this, have a lookup table of powers of 10 (e.g. 1, 10, 100, 1000, 10000, ....), find the largest divisor that's not larger than the mantissa, then do something vaguely like while(base2_mantissa > 0) { divisor = find_largest_divisor_from_table(base2_mantissa); *dest = base2_mantissa / divisor + '0'; base2_mantissa %= divisor; dest++; }. This allows you to quit early - e.g. if you only need a maximum of the 10 significant digits then you can use a fixed size buffer of 10 char and stop when you have 10 digits. Of course all of this (the table of divisors, etc) will need to be using "big integer" too.

The final step is printing "base 10 mantissa" while making sure you insert the decimal point character ('.') where "base 10 exponent" says it should be. If you used "most significant digit to least significant digit" in the previous step then this can be combined with the previous step (e.g. print one digit at a time without storing "base 10 mastissa" at all, while inserting the decimal point in the right place).

For other floating point operations:

Negation is trivial (an XOR of the sign bit).

For everything else, do it in 3 phases - split the source values into "signed integer mantissa" and "signed integer exponent" (mostly as described above); do a "middle phase" that depends on what the operation is; then combine the resulting "signed integer mantissa" and "signed integer exponent" back into the right format.

As a general guide (without complications - overflow, underflow, rounding, NaNs, ...) the "middle phases" are:

  • for multiplication: result_mantissa = src1_mantissa * src2_mantissa; result_exponent = src1_exponent + src2_exponent - mantissa_size;

  • for division: result_mantissa = (src1_mantissa << mantissa_size) / src2_mantissa; result_exponent = src1_exponent - src2_exponent;

  • for addition: find the value with the most positive exponent; shift the other value's mantissa right while increasing its exponent until the exponents match; result_mantissa = src1_mantissa + src2_mantissa; result_exponent = src1_exponent;

  • for subtraction: negate the second value and use addition instead

  • for shift left: if the mantissa is too small (sub-normal) shift it left as much as you can (while making sure it doesn't become too large); then add the remaining shift count to the exponent.

  • for shift right: subtract the shift count from the exponent as much as you can while keeping the exponent from becoming "too negative". If there's any shift count left then shift the mantissa right (forming a sub-normal).

Note that combining the resulting "signed integer mantissa" and "signed integer exponent" back into the right format should include some kind of while(result_mantissa's magnitude is too big) { result_mantissa >> 1; result_exponent++); }; which ends up being a little messy (can/should take into account rounding mode).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文