第35章 温度转换

入门级的编程书籍一般都介绍“华氏温度转换为摄氏温度”的例子。

从华氏度转换成摄氏度的计算公式为

 C=\frac{5\cdot (F-32)}{9}

笔者对程序添加了简单的出错处理:

① 输入的温度数必须正确。

② 核查最终结果,确保不会出现绝对零度(−273℃)以下的摄氏温度值。这是中学物理学课本介绍过的一个常识。

说明:调用函数exit()会立即退出本程序,而且不会向调用方函数返回任何值。

35.1 整数值

#include <stdio.h>
#include <stdlib.h>

int main()
{
          int celsius, fahr;
          printf ("Enter temperature in Fahrenheit:\n");
          if (scanf ("%d", &fahr)!=1)
          {
                    printf ("Error while parsing your input\n");
                    exit(0);
          };

          celsius = 5 * (fahr-32) / 9;

          if (celsius<-273)
          {
                    printf ("Error: incorrect temperature!\n");
                    exit(0);
          };
          printf ("Celsius: %d\n", celsius);
};
35.1.1 x86构架下MSVC 2012优化

指令清单35.1 x86构架下MSVC 2012优化

$SG4228 DB      'Enter temperature in Fahrenheit:', 0aH, 00H
$SG4230 DB      '%d', 00H
$SG4231 DB      'Error while parsing your input', 0aH, 00H
$SG4233 DB      'Error: incorrect temperature!', 0aH, 00H
$SG4234 DB      'Celsius: %d', 0aH, 00H

_fahr$ = -4                                            ; size = 4
_main   PROC
        push    ecx
        push    esi
        mov     esi, DWORD PTR __imp__printf
        push    OFFSET $SG4228            ; 'Enter temperature in Fahrenheit:'
        call    esi                       ; call printf()
        lea     eax, DWORD PTR _fahr$[esp+12]
        push    eax
        push    OFFSET $SG4230            ; '%d'
        call    DWORD PTR __imp__scanf
        add     esp, 12                                ; 0000000cH
        cmp     eax, 1
        je      SHORT $LN2@main
        push    OFFSET $SG4231             ; 'Error while parsing your input'
        call    esi                        ; call printf()
        add     esp, 4
        push    0
        call    DWORD PTR __imp__exit
$LN9@main:
$LN2@main:
        mov    eax, DWORD PTR _fahr$[esp+8]
        add    eax, -32                                 ; ffffffe0H
        lea    ecx, DWORD PTR [eax+eax*4]
        mov    eax, 954437177                           ; 38e38e39H
        imul   ecx
        sar    edx, 1
        mov    eax, edx
        shr    eax, 31                                  ; 0000001fH
        add    eax, edx
        cmp    eax, -273                                ; fffffeefH
        jge    SHORT $LN1@main
        push   OFFSET $SG4233             ; 'Error: incorrect temperature!'
        call   esi                        ; call printf()
        add    esp, 4
        push   0
        call   DWORD PTR __imp__exit
$LN10@main:
$LN1@main:
        push   eax
        push   OFFSET $SG4234             ; 'Celsius: %d'
        call   esi                        ; call printf()
        add    esp, 8
        ; return 0 - by C99 standard
        xor    eax, eax
        pop    esi
        pop    ecx
        ret    0
$LN8@main:
_main   ENDP

必须说明的是:

35.1.2 x64构架下的MSVC 2012优化

x64的代码和x86的代码大体相同。只是每次调用exit()函数之后,都有一个INT 3指令。

        xor      ecx, ecx
        call     QWORD PTR __imp_exit
        int      3

INT 3是调试器debugger的断点设置指令。

当程序执行exit()函数之后,它就不会再返回到原程序,而是直接退出了。编译器大概认为,在发生异常退出的情况下,通常人们应当使用调试器分析异常情况吧。

35.2 浮点数运算

#include <stdio.h>
#include <stdlib.h>

int main()
{
         double celsius, fahr;
         printf ("Enter temperature in Fahrenheit:\n");
         if (scanf ("%lf", &fahr)!=1)
         {
                  printf ("Error while parsing your input\n");
                  exit(0);
         };

         celsius = 5 * (fahr-32) / 9;

         if (celsius<-273)
         {
                  printf ("Error: incorrect temperature!\n");
                  exit(0);
         };
         printf ("Celsius: %lf\n", celsius);
};

MSVC 2010 x86采用的是FPU指令。

指令清单35.2 MSVC 2010 x86优化

$SG4038 DB     'Enter temperature in Fahrenheit:', 0aH, 00H
$SG4040 DB     '%lf', 00H
$SG4041 DB     'Error while parsing your input', 0aH, 00H
$SG4043 DB     'Error: incorrect temperature!', 0aH, 00H
$SG4044 DB     'Celsius: %lf', 0aH, 00H

__real@c071100000000000 DQ 0c071100000000000r     ; -273
__real@4022000000000000 DQ 04022000000000000r     ; 9
__real@4014000000000000 DQ 04014000000000000r     ; 5
__real@4040000000000000 DQ 04040000000000000r     ; 32

_fahr$ = -8                                       ; size = 8
_main PROC
      sub     esp, 8
      push    esi
      mov     esi, DWORD PTR __imp__printf
      push    OFFSET $SG4038          ; 'Enter temperature in Fahrenheit:'
      call    esi                     ; call printf()
      lea     eax, DWORD PTR _fahr$[esp+16]
      push    eax
      push    OFFSET $SG4040          ; '%lf'
      call    DWORD PTR __imp__scanf
      add     esp, 12                                    ; 0000000cH
      cmp     eax, 1
      je      SHORT $LN2@main
      push    OFFSET $SG4041          ; 'Error while parsing your input'
      call    esi                     ; call printf()
      add     esp, 4
      push    0
      call    DWORD PTR __imp__exit
$LN2@main:
      fld     QWORD PTR _fahr$[esp+12]
      fsub    QWORD PTR __real@4040000000000000 ; 32
      fmul    QWORD PTR __real@4014000000000000 ; 5
      fdiv    QWORD PTR __real@4022000000000000 ; 9
      fld     QWORD PTR __real@c071100000000000 ; -273
      fcomp   ST(1)
      fnstsw  ax
      test    ah, 65                                     ; 00000041H
      jne     SHORT $LN1@main
      push    OFFSET $SG4043         ; 'Error: incorrect temperature!'
      fstp    ST(0)
      call    esi                    ; call printf()
      add     esp, 4
      push    0
      call    DWORD PTR __imp__exit
$LN1@main:
      sub     esp, 8
      fstp    QWORD PTR [esp]
      push    OFFSET $SG4044         ; 'Celsius: %lf'
      call    esi
      add     esp, 12                                    ; 0000000cH
      ; return 0 - by C99 standard
      xor     eax, eax
      pop     esi
      add     esp, 8
      ret     0
$LN10@main:
_main ENDP

但MSVC 2012分配的却是SIMD指令。

指令清单35.3 MSVC 2012 x86优化

$SG4228 DB      'Enter temperature in Fahrenheit:', 0aH, 00H
$SG4230 DB      '%lf', 00H
$SG4231 DB      'Error while parsing your input', 0aH, 00H
$SG4233 DB      'Error: incorrect temperature!', 0aH, 00H
$SG4234 DB      'Celsius: %lf', 0aH, 00H
__real@c071100000000000 DQ 0c071100000000000r   ; -273
__real@4040000000000000 DQ 04040000000000000r   ; 32
__real@4022000000000000 DQ 04022000000000000r   ; 9
__real@4014000000000000 DQ 04014000000000000r   ; 5

_fahr$ = -8                                         ; size = 8
_main    PROC
         sub     esp, 8
         push    esi
         mov     esi, DWORD PTR __imp__printf
         push    OFFSET $SG4228           ; 'Enter temperature in Fahrenheit:'
         call    esi                      ; call printf()
         lea     eax, DWORD PTR _fahr$[esp+16]
         push    eax
         push    OFFSET $SG4230           ; '%lf'
         call    DWORD PTR __imp__scanf
         add     esp, 12                             ; 0000000cH
         cmp     eax, 1
         je      SHORT $LN2@main
         push    OFFSET $SG4231           ; 'Error while parsing your input'
         call    esi                      ; call printf()
         add     esp, 4
         push    0
         call    DWORD PTR __imp__exit
$LN9@main:
$LN2@main:
         movsd   xmm1, QWORD PTR _fahr$[esp+12]
         subsd   xmm1, QWORD PTR __real@4040000000000000 ; 32
         movsd   xmm0, QWORD PTR __real@c071100000000000 ; -273
         mulsd   xmm1, QWORD PTR __real@4014000000000000 ; 5
         divsd   xmm1, QWORD PTR __real@4022000000000000 ; 9
         comisd  xmm0, xmm1
         jbe     SHORT $LN1@main
         push    OFFSET $SG4233           ; 'Error: incorrect temperature!'
         call    esi                      ; call printf()
         add     esp, 4
         push    0
         call    DWORD PTR __imp__exit
$LN10@main:
$LN1@main:
         sub     esp, 8
         movsd   QWORD PTR [esp], xmm1
         push    OFFSET $SG4234           ; 'Celsius: %lf'
         call    esi                      ; call printf()
         add     esp, 12                                ; 0000000cH
         ; return 0 - by C99 standard
         xor     eax, eax
         pop     esi
         add     esp, 8
         ret     0
$LN8@main:
_main ENDP

当然,x86的指令集确实支持SIMD指令,浮点数运算也毫无问题。大概是这种方式的计算指令比较简单,所以微软的编译器分配了SIMD指令。

我们还注意到绝对零度−273,早早地就导入了寄存器XMM0。这也没关系,编译器不是按照源代码的书写顺序分配的汇编指令。