英文:
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 |
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论