Почему результат этого явного приведения отличается от неявного? - PullRequest
12 голосов
/ 15 апреля 2009

Почему результат этого явного приведения отличается от неявного?

#include <stdio.h>

double  a;
double  b;
double  c;

long    d;

double    e;

int main() {
    a = 1.0;
    b = 2.0;
    c = .1;

    d = (b - a + c) / c;
    printf("%li\n", d);        //    10

    e = (b - a + c) / c;
    d = (long) e;
    printf("%li\n", d);        //    11
    }

Если я делаю d = (long) ((b - a + c) / c); Я также получаю 10. Почему назначение на удвоение имеет значение?

Ответы [ 5 ]

16 голосов
/ 15 апреля 2009

Я подозреваю, что разница заключается в преобразовании 80-разрядного значения с плавающей запятой в длинное по сравнению с преобразованием из 80-разрядного значения с плавающей запятой в 64-разрядное и , затем преобразование в длинный.

(Причиной появления 80 битов является то, что это типичная точность, используемая для фактической арифметики и ширины регистров с плавающей запятой.)

Предположим, что 80-битный результат - что-то вроде 10.999999999999999 - преобразование из этого значения в длинное дает 10. Однако самое близкое 64-битное значение с плавающей запятой к 80-битному значению фактически равно 11.0, поэтому двухэтапное преобразование в конечном итоге уступает 11.

РЕДАКТИРОВАТЬ: Чтобы придать этому немного больший вес ...

Вот программа на Java, которая использует арифметику произвольной точности для того же вычисления. Обратите внимание, что он преобразует значение типа Double, ближайшее к 0,1, в значение BigDecimal - это значение равно 0,1000000000000000055511151231257827021181583404541015625. (Другими словами, точный результат вычисления равен , а не 11.)

import java.math.*;

public class Test
{
    public static void main(String[] args)
    {
        BigDecimal c = new BigDecimal(0.1d);        
        BigDecimal a = new BigDecimal(1d);
        BigDecimal b = new BigDecimal(2d);

        BigDecimal result = b.subtract(a)
                             .add(c)
                             .divide(c, 40, RoundingMode.FLOOR);
        System.out.println(result);
    }
}

Вот результат:

10.9999999999999994448884876874217606030632

Другими словами, это правильно с точностью до 40 десятичных цифр (гораздо больше, чем может обработать 64-битная или 80-битная плавающая точка).

Теперь давайте рассмотрим, как выглядит это число в двоичном виде. У меня нет никаких инструментов, чтобы легко сделать преобразование, но снова мы можем использовать Java, чтобы помочь. Предполагая нормализованное число, часть «10» заканчивается использованием трех битов (один меньше, чем для одиннадцати = 1011). Это оставляет 60 бит мантиссы для расширенной точности (80 бит) и 48 бит для двойной точности (64 бит).

Итак, какое число ближе к 11 в каждой точности? Опять же, давайте использовать Java:

import java.math.*;

public class Test
{
    public static void main(String[] args)
    {
        BigDecimal half = new BigDecimal("0.5");        
        BigDecimal eleven = new BigDecimal(11);

        System.out.println(eleven.subtract(half.pow(60)));
        System.out.println(eleven.subtract(half.pow(48)));        
    }
}

Результаты:

10.999999999999999999132638262011596452794037759304046630859375
10.999999999999996447286321199499070644378662109375

Итак, у нас есть три числа:

Correct value: 10.999999999999999444888487687421760603063...
11-2^(-60): 10.999999999999999999132638262011596452794037759304046630859375
11-2^(-48): 10.999999999999996447286321199499070644378662109375

Теперь вычислите наиболее близкое к правильному значение для каждой точности - для расширенной точности оно меньше, чем 11. Округлите каждое из этих значений до длинного, и вы получите 10 и 11 соответственно.

Надеюсь, этого достаточно, чтобы убедить сомневающихся;)

2 голосов
/ 15 апреля 2009

Я получаю 10 и 11 в моей 32-битной системе x86 linux, работающей с gcc 4.3.2.

Соответствующий C / ASM здесь:

26:foo.c         ****     d = (b - a + c) / c;                                               
  42                            .loc 1 26 0
  43 0031 DD050000              fldl    b
  43      0000
  44 0037 DD050000              fldl    a
  44      0000
  45 003d DEE9                  fsubrp  %st, %st(1)
  46 003f DD050000              fldl    c
  46      0000
  47 0045 DEC1                  faddp   %st, %st(1)
  48 0047 DD050000              fldl    c
  48      0000
  49 004d DEF9                  fdivrp  %st, %st(1)
  50 004f D97DFA                fnstcw  -6(%ebp)
  51 0052 0FB745FA              movzwl  -6(%ebp), %eax
  52 0056 B40C                  movb    $12, %ah
  53 0058 668945F8              movw    %ax, -8(%ebp)
  54 005c D96DF8                fldcw   -8(%ebp)
  55 005f DB5DF4                fistpl  -12(%ebp)
  56 0062 D96DFA                fldcw   -6(%ebp)
  57 0065 8B45F4                movl    -12(%ebp), %eax
  58 0068 A3000000              movl    %eax, d
  58      00
  27:foo.c         ****
  28:foo.c         ****     printf("%li\n", d);                                                
  59                            .loc 1 28 0
  60 006d A1000000              movl    d, %eax
  60      00
  61 0072 89442404              movl    %eax, 4(%esp)
  62 0076 C7042400              movl    $.LC3, (%esp)
  62      000000
  63 007d E8FCFFFF              call    printf
  63      FF
  29:foo.c         ****     //    10                                                           
  30:foo.c         ****
  31:foo.c         ****     e = (b - a + c) / c;                                               
  64                            .loc 1 31 0
  65 0082 DD050000              fldl    b
  65      0000
  66 0088 DD050000              fldl    a
  66      0000
  67 008e DEE9                  fsubrp  %st, %st(1)
  68 0090 DD050000              fldl    c
  68      0000
  69 0096 DEC1                  faddp   %st, %st(1)
  70 0098 DD050000              fldl    c
  70      0000
  71 009e DEF9                  fdivrp  %st, %st(1)
  72 00a0 DD1D0000              fstpl   e
  72      0000
  32:foo.c         ****
  33:foo.c         ****     d = (long) e;                                                      
  73                            .loc 1 33 0
  74 00a6 DD050000              fldl    e
  74      0000
  75 00ac D97DFA                fnstcw  -6(%ebp)
  76 00af 0FB745FA              movzwl  -6(%ebp), %eax
  77 00b3 B40C                  movb    $12, %ah
  78 00b5 668945F8              movw    %ax, -8(%ebp)
  79 00b9 D96DF8                fldcw   -8(%ebp)
  80 00bc DB5DF4                fistpl  -12(%ebp)
  81 00bf D96DFA                fldcw   -6(%ebp)
  82 00c2 8B45F4                movl    -12(%ebp), %eax
  83 00c5 A3000000              movl    %eax, d
  83      00

Ответ оставлен в качестве упражнения для заинтересованного читателя.

1 голос
/ 15 апреля 2009

codepad.org (gcc 4.1.2) переворачивает результаты вашего примера, в то время как в моей локальной системе (gcc 4.3.2) я получаю 11 в обоих случаях. Это наводит меня на мысль, что это проблема с плавающей запятой. Альтернативно, теоретически это может быть усечение (b - a + c), которое в целочисленном контексте будет иметь значение (2 - 1 + 0) / .1, что будет 10, тогда как в контексте с плавающей запятой (2.0 - 1.0 + 0.1) ) / .1 = 1.1 / .1 = 11. Хотя это было бы странно.

0 голосов
/ 15 апреля 2009

Вот куча подробностей о проблемах с плавающей запятой и действительно хорошая статья. Но, в принципе, не все значения с плавающей запятой могут быть представлены определенным количеством битов (32 или 64 бит). или что угодно). Это глубокая тема, но мне она нравится, потому что она напоминает мне Проф. Каган . :)

0 голосов
/ 15 апреля 2009

Прямое копирование / вставка и компиляция в Linux дает мне 11 для обоих. Добавление d = (long) ((b - a + c) / c); также дает 11. То же самое относится и к OpenBSD.

...