Using assembly to convert the temperature from Celsius into Fahrenheit, and back again

huangapple go评论57阅读模式
英文:

Using assembly to convert the temperature from Celsius into Fahrenheit, and back again

问题

I am using the following x86 assembly instructions to convert from degrees Celsius into degrees Fahrenheit:

  imul dword [NINE]
  idiv dword [FIVE]
  add  eax, 32

For rather a lot of inputs this produces an off-by-one error:

 1 ℃ produces 33 ℉, but expecting 34 ℉ (33.8 ℉)
-1 ℃ produces 31 ℉, but expecting 30 ℉ (30.2 ℉)

For now, I solved the issue through inspection of the remainder from the signed division by 5 and then rounding accordingly:

  imul dword [NINE]
  idiv dword [FIVE]   ; -> Remainder EDX=[-4,4]
  add  eax, 32+1
  cmp  edx, 2
  jg   @f
  dec  eax
  cmp  edx, -2
  jge  @f
  dec  eax
@@:

I would very much prefer a branchless solution since I've heard it would be faster, generally speaking. Additionally, how can I change the code so it returns a decimal fraction?

英文:

I am using the following x86 assembly instructions to convert from degrees Celsius into degrees Fahrenheit:

  imul dword [NINE]
  idiv dword [FIVE]
  add  eax, 32

For rather a lot of inputs this produces an off-by-one error:

> 1 °C produces 33 °F, but expecting 34 °F (33.8 °F)
> -1 °C produces 31 °F, but expecting 30 °F (30.2 °F)

For now, I solved the issue through inspection of the remainder from the signed division by 5 and then rounding accordingly:

  imul dword [NINE]
  idiv dword [FIVE]   ; -> Remainder EDX=[-4,4]
  add  eax, 32+1
  cmp  edx, 2
  jg   @f
  dec  eax
  cmp  edx, -2
  jge  @f
  dec  eax
@@:

I would very much prefer a branchless solution since I've heard it would be faster, generally speaking.
Additionally, how can I change the code so it returns a decimal fraction?

答案1

得分: 3

从摄氏度到华氏度

使用x86汇编的直接实现将摄氏度转换为华氏度,使用下面的公式:

华氏度 = (摄氏度 * 9 / 5) + 32

; 输入 (eax) 输出 (eax) 修改 (edx)
C2F_a:  imul    dword [NINE]
        idiv    dword [FIVE]
        add     eax, 32
        ret

需要使用有符号操作,因为摄氏度和华氏度都存在于负数范围内。它们的刻度从绝对零度(0 K)开始:摄氏度从-273.15°C开始,华氏度从-459.67°F开始。

四舍五入

上述代码的一个重要缺点是结果没有被适当地四舍五入到最接近的整数(idiv向零舍入,也称为截断)。实现这种类型的四舍五入的最简单方法是在除以5之前将被除数加2。遗憾的是,这仅适用于正数输入。对于负数输入,我们需要在除以5之前减去2。因此,下一个解决方案通过首先向输入添加适当的偏差来避免有符号除法。代码的其余部分不再需要考虑负数。偏差为275将使任何合法输入都为正数,并且不会引入进一步的四舍五入问题,因为最终将去除偏差的值是整数:(275 * 9 / 5)= 495。

华氏度 = (((摄氏度 + 275) * 9) + 2) / 5 + (32 - 495)

; 输入 (eax) 输出 (eax) 修改 (edx)
C2F_b:  add     eax, 275
        imul    eax, 9
        add     eax, 2
        xor     edx, edx
        div     dword [FIVE]
        add     eax, 32-495
        ret

优化

可以用LEA指令替换乘以9的乘法,同时吸收多个加法。同样,可以用5的乘法逆元替换除以5。

华氏度 = (((摄氏度 + 275) * 9) + 2) / 5 + (32 - 495)

; 输入 (eax) 输出 (eax) 修改 (edx)
C2F_c:  lea     eax, [eax+eax*8+(275*9)+2]
        mov     edx, 0CCCCCCCDh
        mul     edx
        shr     edx, 2
        lea     eax, [edx+32-495]
        ret

精度

使用小数定点数,缩放为1/100,我们可以提供精确到2位小数的结果。例如,-273.15°C以带符号整数-27315存储,将输出带符号整数-45967,表示-459.67°F。

华氏度 = (((摄氏度 + 27500) * 9) + 2) / 5 + (3200 - 49500)

; 输入 (eax) 输出 (eax) 修改 (edx)
C2F_d:  lea     eax, [eax+eax*8+(27500*9)+2]
        mov     edx, 0CCCCCCCDh
        mul     edx
        shr     edx, 2
        lea     eax, [edx+3200-49500]
        ret

从华氏度到摄氏度

使用x86汇编的直接实现将华氏度转换为摄氏度,使用下面的公式:

摄氏度 = (华氏度 - 32) * 5 / 9

; 输入 (eax) 输出 (eax) 修改 (edx)
F2C_a:  sub     eax, 32
        imul    dword [FIVE]
        idiv    dword [NINE]
        ret

需要使用有符号操作,因为摄氏度和华氏度都存在于负数范围内。它们的刻度从绝对零度(0 K)开始:摄氏度从-273.15°C开始,华氏度从-459.67°F开始。

四舍五入

上述代码的一个重要缺点是结果没有被适当地四舍五入到最接近的整数。实现这种类型的四舍五入的最简单方法是在除以9之前将被除数加4。遗憾的是,这仅适用于正数输入。对于负数输入,我们需要在除以9之前减去4。因此,下一个解决方案通过首先向输入添加适当的偏差来避免有符号除法。代码的其余部分不再需要考虑负数。偏差为495将使任何合法输入都为正数,并且不会引入进一步的四舍五入问题,因为最终将去除偏差的值是整数:(495 * 5 / 9)= 275。

摄氏度 = (((华氏度 + 495 - 32) * 5) + 4) / 9 - 275

; 输入 (eax) 输出 (eax) 修改 (edx)
F2C_b:  add     eax, 495-32
        imul    eax, 5
        add     eax, 4
        xor     edx, edx
        div     dword [NINE]
        sub     eax, 275
        ret

优化

可以用LEA指令替换乘以5的乘法,同时吸收多个加法。同样,可以

英文:

From Celsius to Fahrenheit

The straightforward implementation in x86 assembly for the conversion from Celsius to Fahrenheit uses next formula:

> Fahrenheit = (Celsius * 9 / 5) + 32

; IN (eax) OUT (eax) MOD (edx)
C2F_a:  imul    dword [NINE]
        idiv    dword [FIVE]
        add     eax, 32
        ret

Signed operations are needed because both Celsius and Fahrenheit also exist in the negative numbers range. Their scales begin at absolute zero (0 K): Celsius starts at -273.15 °C and Fahrenheit starts at -459.67 °F.

Rounding

An important drawback of the above code is that the results don't get properly rounded to the nearest integer. (idiv rounds toward zero, aka truncation.) The simplest way to achieve this kind of rounding would be to add 2 to the dividend prior to dividing by 5. Sadly, this only works well for positive inputs. For negative inputs, we would rather need to subtract 2 before dividing by 5.
Next solution therefore avoids signed division by first adding a suitable bias to the input. The remainder of the code no longer needs to consider negative numbers. A bias of 275 will render any legal input positive, and will not introduce a further rounding issue because the value that will eventually remove the bias is an integer: (275 * 9 / 5) = 495.

> Fahrenheit = (((Celsius + 275) * 9) + 2) / 5 + (32 - 495)

; IN (eax) OUT (eax) MOD (edx)
C2F_b:  add     eax, 275
        imul    eax, 9
        add     eax, 2
        xor     edx, edx
        div     dword [FIVE]
        add     eax, 32-495
        ret

Optimization

It is possible to replace the multiplication by 9 with a LEA instruction that at the same time absorbs several additions.
It is equally possible to replace the division by 5 with a multiplication by 5's multiplicative inverse.

> Fahrenheit = (((Celsius + 275) * 9) + 2) / 5 + (32 - 495)

; IN (eax) OUT (eax) MOD (edx)
C2F_c:  lea     eax, [eax+eax*8+(275*9)+2]
        mov     edx, 0CCCCCCCDh
        mul     edx
        shr     edx, 2
        lea     eax, [edx+32-495]
        ret

Precision

Using decimal fixed-point numbers with scaling 1/100, we can deliver results that are accurate to 2 decimal places. eg. -273.15 °C gets stored as the signed integer -27315, and will output the signed integer -45967 which represents -459.67 °F.

> Fahrenheit = (((Celsius + 27500) * 9) + 2) / 5 + (3200 - 49500)

; IN (eax) OUT (eax) MOD (edx)
C2F_d:  lea     eax, [eax+eax*8+(27500*9)+2]
        mov     edx, 0CCCCCCCDh
        mul     edx
        shr     edx, 2
        lea     eax, [edx+3200-49500]
        ret
°C C2F_a C2F_b C2F_c C2F_d Sharp®
-10 14 14 14 14.00 14
-9 16 16 16 15.80 15.8
-8 18 18 18 17.60 17.6
-7 20! 19 19 19.40 19.4
-6 22! 21 21 21.20 21.2
-5 23 23 23 23.00 23
-4 25 25 25 24.80 24.8
-3 27 27 27 26.60 26.6
-2 29! 28 28 28.40 28.4
-1 31! 30 30 30.20 30.2
0 32 32 32 32.00 32
1 33! 34 34 33.80 33.8
2 35! 36 36 35.60 35.6
3 37 37 37 37.40 37.4
4 39 39 39 39.20 39.2
5 41 41 41 41.00 41
6 42! 43 43 42.80 42.8
7 44! 45 45 44.60 44.6
8 46 46 46 46.40 46.4
9 48 48 48 48.20 48.2
10 50 50 50 50.00 50

From Fahrenheit to Celsius

The straightforward implementation in x86 assembly for the conversion from
Fahrenheit to Celsius uses next formula:

> Celsius = (Fahrenheit - 32) * 5 / 9

; IN (eax) OUT (eax) MOD (edx)
F2C_a:  sub     eax, 32
        imul    dword [FIVE]
        idiv    dword [NINE]
        ret

Signed operations are needed because both Celsius and Fahrenheit also exist in the negative numbers range. Their scales begin at absolute zero (0 K): Celsius starts at -273.15 °C and Fahrenheit starts at -459.67 °F.

Rounding

An important drawback of the above code is that the results don't get properly rounded to the nearest integer. The simplest way to achieve this kind of rounding would be to add 4 to the dividend prior to dividing by 9. Sadly, this only works well for positive inputs. For negative inputs, we would rather need to subtract 4 before dividing by 9.
Next solution therefore avoids signed division by first adding a suitable bias to the input. The remainder of the code no longer needs to consider negative numbers. A bias of 495 will render any legal input positive, and will not introduce a further rounding issue because the value that will eventually remove the bias is an integer: (495 * 5 / 9) = 275.

> Celsius = (((Fahrenheit + 495 - 32) * 5) + 4) / 9 - 275

; IN (eax) OUT (eax) MOD (edx)
F2C_b:  add     eax, 495-32
        imul    eax, 5
        add     eax, 4
        xor     edx, edx
        div     dword [NINE]
        sub     eax, 275
        ret

Optimization

It is possible to replace the multiplication by 5 with a LEA instruction that at the same time absorbs several additions.
It is equally possible to replace the division by 9 with a multiplication by 9's multiplicative inverse.

> Celsius = (((Fahrenheit + 495 - 32) * 5) + 4) / 9 - 275

; IN (eax) OUT (eax) MOD (edx)
F2C_c:  lea     eax, [eax+eax*4+(495-32)*5+4]
        mov     edx, 38E38E39h
        mul     edx
        shr     edx, 1
        lea     eax, [edx-275]
        ret

Precision

Using decimal fixed-point numbers with scaling 1/100, we can deliver results that are accurate to 2 decimal places. eg. -459.67 °F gets stored as the signed integer -45967, and will output the signed integer -27315 which represents -273.15 °C.

> Celsius = (((Fahrenheit + 49500 - 3200) * 5) + 4) / 9 - 27500

; IN (eax) OUT (eax) MOD (edx)
F2C_d:  lea     eax, [eax+eax*4+(49500-3200)*5+4]
        mov     edx, 38E38E39h
        mul     edx
        shr     edx, 1
        lea     eax, [edx-27500]
        ret
°F F2C_a F2C_b F2C_c F2C_d Sharp®
-10 -23 -23 -23 -23.33 -23.333333
-9 -22! -23 -23 -22.78 -22.777777
-8 -22 -22 -22 -22.22 -22.222222
-7 -21! -22 -22 -21.67 -21.666666
-6 -21 -21 -21 -21.11 -21.111111
-5 -20! -21 -21 -20.56 -20.555555
-4 -20 -20 -20 -20.00 -20
-3 -19 -19 -19 -19.44 -19.444444
-2 -18! -19 -19 -18.89 -18.888888
-1 -18 -18 -18 -18.33 -18.333333
0 -17! -18 -18 -17.78 -17.777777
1 -17 -17 -17 -17.22 -17.222222
2 -16! -17 -17 -16.67 -16.666666
3 -16 -16 -16 -16.11 -16.111111
4 -15! -16 -16 -15.56 -15.555555
5 -15 -15 -15 -15.00 -15
6 -14 -14 -14 -14.44 -14.444444
7 -13! -14 -14 -13.89 -13.888888
8 -13 -13 -13 -13.33 -13.333333
9 -12! -13 -13 -12.78 -12.777777
10 -12 -12 -12 -12.22 -12.222222

huangapple
  • 本文由 发表于 2023年5月17日 18:34:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76271157.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定