Форум программистов, компьютерный форум CyberForum.ru

Компилятор сломался или ошибка? - C++

Восстановить пароль Регистрация
 
 
Рейтинг: Рейтинг темы: голосов - 16, средняя оценка - 4.75
gmb124
8 / 4 / 1
Регистрация: 11.04.2012
Сообщений: 54
26.07.2012, 17:08     Компилятор сломался или ошибка? #1
Требуется написать программу, которая делает перевод вещественного числа в строку.
Возникает ошибка во время получения дробной части числа.
Например:

C++
1
2
3
double f = 12.23;
int whole = (int) f; //=12
double real = f - whole;  // должно быть 0.23 но может получиться = 0.2300000012
Почему так и как это можно исправить?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//обращение строки
void RevStr(char *str){
    char *start,*end;
    char t;
    
    start = str;
    end = &str[strlen(str) -1];
 
    while(end > start){
        t = *end;
        *end = *start;
        *start = t;
        start++; end--;
    }
}
 
//перевод вещественного числа в строку
char * ConvertToString(double f){
    char result[80];
    int whole;
    double real;
    
    whole = (int) f;    //переменная для целой части
    real = f - whole;   //переменная для вещественной части
    
    int i = 0;
    
    //целую часть заносим в строку в обратном порядке
    do{
        result[i] = (whole % 10) + '0';
        i++;
    }while(whole /= 10);
    
    result[i] = 0;
    
    //развернём строку
    RevStr(result);
 
    result[i++] = '.';
    
 
    if(real == 0){
        result[i] = result[i+1] = '0';
        result[i+2] = 0;
        return result;
    }
    
    int serv;
 
 
    //заносим в строку вещественную часть
    while(real){
        serv = (int)(real * 10);
        result[i] = serv + '0';
        real = real * 10 - serv;
        i++;
    }
 
    result[i] = 0;
    return result;
}
Лучшие ответы (1)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
easybudda
Модератор
Эксперт С++
 Аватар для easybudda
9372 / 5422 / 914
Регистрация: 25.07.2009
Сообщений: 10,423
27.07.2012, 16:10     Компилятор сломался или ошибка? #21
Цитата Сообщение от kravam Посмотреть сообщение
Сдаётся мне, вещественные числа представлены в памяти компьютера однозначно.
Это, если не помнить, что есть иррациональные числа. Как можно однозначно корень из двух представить?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <iomanip>
#include <cmath>
 
int main() {
    const int PRECISION(20);
    float f = sqrt(2.0);
    double d = sqrt(2.0);
    
    std::cout << std::fixed << std::setprecision(PRECISION) << f << std::endl;
    std::cout << std::fixed << std::setprecision(PRECISION) << d << std::endl;
    
    return 0;
}
Код
./sqrt2 
1.41421353816986083984
1.41421356237309514547
Точность представления всегда от способа хранения будет зависеть. При чём, если с размером памяти для хранения float или double какая-то ясность существует, с типом long double всё совсем не однозначно.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16825 / 5246 / 321
Регистрация: 30.03.2009
Сообщений: 14,127
Записей в блоге: 26
27.07.2012, 16:17     Компилятор сломался или ошибка? #22
Цитата Сообщение от easybudda Посмотреть сообщение
Это, если не помнить, что есть иррациональные числа
У людей другая проблема. Они пишут вполне себе рациональное число (12.23 в данном примере), вычитают из него другое рациональное число 12 и получают совсем не то, что ожидали. А проблема кроется в том, что число 1.23 имеет конечную десятичную запись в десятичной системе счисления, но в двоичной системе запись будет бесконечной (а потому становится неточной при любомокруглении). И такие вещи с непривычки сложно устаканить в голове.

Можно обратный пример сделать. Если взять число 1/3, то в троичной системе счисления это будет записываться как конечное "0.1", но в десятичной системе - как бесконечное "0.33333..."
kravam
быдлокодер
 Аватар для kravam
1512 / 872 / 44
Регистрация: 04.06.2008
Сообщений: 5,271
27.07.2012, 20:35     Компилятор сломался или ошибка? #23
Evg, речь шла не об этом
C++
1
2
0.14286
0.142857
И даже совсем не об этом. Тут, наверное такое различие из за разности количества знаков и из-за округления. Речь шла вот о чём
Как из числа double 4.056 извлечь число 1000?

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Увидев вывод easybudda
C++
1
2
1.41421353816986083984
1.41421356237309514547
Я подумал было, что он понял меня. Но, присмотревшись к его коду увидел, что у него числа разного типа, double и float. Я же говорю о случае, когда разнятся числа одного типа (точнее разна их печать), точнее- печать их дробной части. И дело, как ты смог увидеть, совсем не в округлении.

Не по теме:

Стал бы я из за банального округления сыр-бор подымать

Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16825 / 5246 / 321
Регистрация: 30.03.2009
Сообщений: 14,127
Записей в блоге: 26
27.07.2012, 23:45     Компилятор сломался или ошибка? #24
Цитата Сообщение от kravam Посмотреть сообщение
И даже совсем не об этом. Тут, наверное такое различие из за разности количества знаков и из-за округления. Речь шла вот о чём
Как из числа double 4.056 извлечь число 1000?
Это всё ровно то же самое. При печати делается округление, т.е. печатается число не совсем такое, какое оно на самом деле. Если при печати ты видишь 4.056000, то это не значит, что дальше нет никаких знаков. Сделай печать 10 знаков после запятой - увидишь свою двоечку
kravam
быдлокодер
 Аватар для kravam
1512 / 872 / 44
Регистрация: 04.06.2008
Сообщений: 5,271
28.07.2012, 00:57     Компилятор сломался или ошибка? #25
Так я об этом и твержу и иду немного глубже. Если бы я эту двоечку видел ВСЕГДА, я бы так и спросил почему я вижу двойку, хотя её там быть не должно.

Весь вопрос в том, что я её вижу через раз! число 4.056 всегда представлена на моём компе одинаково. А печатается по-разному.
И почему так, я не пойму.
Или комп всякий раз по-разному округляет?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16825 / 5246 / 321
Регистрация: 30.03.2009
Сообщений: 14,127
Записей в блоге: 26
28.07.2012, 01:00     Компилятор сломался или ошибка? #26
Цитата Сообщение от kravam Посмотреть сообщение
Весь вопрос в том, что я её вижу через раз!
Пруф в студию
kravam
быдлокодер
 Аватар для kravam
1512 / 872 / 44
Регистрация: 04.06.2008
Сообщений: 5,271
28.07.2012, 01:09     Компилятор сломался или ошибка? #27
Никакого пруфа, как я тебе докажу?
Хотя может ты и прав, если имеешь ввиду, что допустим, если есть "лишняя" двойка, то она есть всегда. Я щас уже не вспомню, как оно было. А скорее всего было так:

Вывожу, допустим 0.4056 со сколькими-то знаками после запятой и вижу ноли там, где они должны быть. Предсказуемая печать то есть
А потом чуть-чуть изменяю 0.4056, множа на 10 и получаю двойку где-нибудь в конце. где её при предыдущей печати не было.
Скорее всего было так. И я делаю вывод, что 0.4056 печатается всякий раз по-разному. Ты мне можешь сказать, что во втором случае это число 4.056. другое то есть. А я тебе отвечу, что и в первом и во втором случае я имел дело с одним и тем же адресом в памяти. И откуда там взялась двойка- ума не приложу.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16825 / 5246 / 321
Регистрация: 30.03.2009
Сообщений: 14,127
Записей в блоге: 26
28.07.2012, 12:44     Компилятор сломался или ошибка? #28
Цитата Сообщение от kravam Посмотреть сообщение
Никакого пруфа, как я тебе докажу?
Из твоих слов "Весь вопрос в том, что я её вижу через раз!" я так понял, что ОДИН И ТОТ ЖЕ код у тебя через раз работает по разному

Цитата Сообщение от kravam Посмотреть сообщение
Вывожу, допустим 0.4056 со сколькими-то знаками после запятой и вижу ноли там, где они должны быть. Предсказуемая печать то есть
А потом чуть-чуть изменяю 0.4056, множа на 10 и получаю двойку где-нибудь в конце
В первом случае ты не видишь двойки не потому, что её там нет, а потому, что printf и cout по умолчанию печатают только до 6 знаков после запятой и двойка находится за пределами этих 6 знаков. Эта двойка она есть всегда, потому что число 0.4506 нельзя в двоичном виде представить в виде конечной записи (как я выше приводил конечную запись "0.1" и бесконечную "0.333..." для одного и того же числа). Другими словами, число со значением ровно 0.4056 в компьютере с двоичной логикой представить точно невозможно. В принципе. А если ты умножишь его на 10, то у тебя в очередной раз получится другое число, т.е. _не_ ровно 4.056, а очередное округлённое значение. Потому что машина в общем случае НЕ может хранить точное значение чисел. А это следует из того, что не для всякого числа, записанного в виде конечной дробной части в десятичной системе счисления, можно получить такую же конечную запись в двоичной системе счисления. Точные представления возможны только для чисел, которые раскладываются в конечный ряд по отрицательным степеням двойки (при условии, что длина этого ряда уложится в задаваемую точность конкретно float'а, double'а или long double'а)

В следующем примере мы печатаем одно и то же представление числа. И разная печать получается не потому, что в каждом случае число выглядит по разному, а потому, что при печати округление идёт до разного количества знаков после запятой. Печать float'а отличается от печати double'а из-за того, что float представляет числа с меньшей точностью (т.е. с бОльшими потерями)

C
#include <stdio.h>
 
int main (void)
{
  double d1 = 0.4056;
  double d2 = d1 * 10;
  float f1 = d1;
  float f2 = f1 * 10;
 
  /* Печать с округлением по умолчанию */
  printf ("d1=%f\n", d1);
 
  /* Печать с округлением до 6 знаков (что совпадает
   * с округлением по умолчанию */
  printf ("d1=%.06f\n", d1);
 
  /* Печать с округлением до 20 знаков */
  printf ("d1=%.020f\n", d1);
 
  /* Печать с округлением по умолчанию */
  printf ("d2=%f\n", d2);
 
  /* Печать с округлением до 6 знаков (что совпадает
   * с округлением по умолчанию */
  printf ("d2=%.06f\n", d2);
 
  /* Печать с округлением до 20 знаков */
  printf ("d2=%.020f\n", d2);
 
 
  /* Далее всё то же самое, но для float,
   * у которого числа представляются менее точно, чем у double'а */
 
  /* Печать с округлением по умолчанию */
  printf ("f1=%f\n", f1);
 
  /* Печать с округлением до 6 знаков (что совпадает
   * с округлением по умолчанию */
  printf ("f1=%.06f\n", f1);
 
  /* Печать с округлением до 20 знаков */
  printf ("f1=%.020f\n", f1);
 
  /* Печать с округлением по умолчанию */
  printf ("f2=%f\n", f2);
 
  /* Печать с округлением до 6 знаков (что совпадает
   * с округлением по умолчанию */
  printf ("f2=%.06f\n", f2);
 
  /* Печать с округлением до 20 знаков */
  printf ("f2=%.020f\n", f2);
 
 
  return 0;
}
Bash
d1=0.405600
d1=0.405600
d1=0.40560000000000001608
d2=4.056000
d2=4.056000
d2=4.05600000000000004974
f1=0.405600
f1=0.405600
f1=0.40560001134872436523
f2=4.056000
f2=4.056000
f2=4.05600023269653320312
kravam
быдлокодер
 Аватар для kravam
1512 / 872 / 44
Регистрация: 04.06.2008
Сообщений: 5,271
28.07.2012, 20:21     Компилятор сломался или ошибка? #29
Цитата Сообщение от Evg Посмотреть сообщение
А если ты умножишь его на 10, то у тебя в очередной раз получится другое число, т.е. _не_ ровно 4.056, а очередное округлённое значение.
если ты говоришь значит так оно и есть, но округлением я всё же это называть бы не стал. Скорее, некоторое преобразование

Было:
Цитата Сообщение от Evg Посмотреть сообщение
d1=0.40560000000000001608
после умноженият на 10 стало:
Цитата Сообщение от Evg Посмотреть сообщение
d2=4.05600000000000004974
Согласись, это не округление? Округление выглядело бы так:
Цитата Сообщение от Evg Посмотреть сообщение
d1=4.05600000000000002000
или так:
Цитата Сообщение от Evg Посмотреть сообщение
d1=4.05600000000000001610
или даже так:
Цитата Сообщение от Evg Посмотреть сообщение
d1=4.05600000000000001600
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Цитата Сообщение от Evg Посмотреть сообщение
Потому что машина в общем случае НЕ может хранить точное значение чисел.
это недостаток машин, а не объективная реальность. Согласен, корень из двух однозначно не представить, хотя бы в силу его бесконечности. К сожалению, я не знаю, как машина определяет, что число double, наверное, какой-то бит отвечает за это. Так-то я бы показал, как в памяти разместить число, которое однозначно интерпретировалось бы как 0.4056
OhMyGodSoLong
~ Эврика! ~
 Аватар для OhMyGodSoLong
1234 / 983 / 42
Регистрация: 24.07.2012
Сообщений: 2,002
28.07.2012, 20:55     Компилятор сломался или ошибка? #30
Цитата Сообщение от kravam Посмотреть сообщение
если ты говоришь значит так оно и есть, но округлением я всё же это называть бы не стал. Скорее, некоторое преобразование
Да, это преобразования называются «потеря значащих цифр» и «двоичное округление».
Цитата Сообщение от kravam Посмотреть сообщение
это недостаток машин, а не объективная реальность. Согласен, корень из двух однозначно не представить, хотя бы в силу его бесконечности. К сожалению, я не знаю, как машина определяет, что число double, наверное, какой-то бит отвечает за это. Так-то я бы показал, как в памяти разместить число, которое однозначно интерпретировалось бы как 0.4056
Всякие float, single и double в подавляющем большинстве случаев представляются и хранятся в памяти в соответствии со стандартом IEEE 754. Потеря точности и так далее — это недостаток данного представления.

Числа с плавающей десятичной точкой, коими и являются double с float, не предназначены для точных вычислений. Они предназначены для представления широкого диапазона чисел. Естественно, они их представляют с погрешностью, но в отличие от целочисленных величин погрешность фиксирована не абсолютная, а относительная. Поэтому они и удобны для всяких физических вычислений, где точнее 10–15 представлять ничего не надо, потому что измерить точнее не получается.

Для точного представления рациональных чисел есть другие приёмы. Например, числа с фиксированной точкой (это когда 0,4056 так и хранится как целое число 4056 с припиской, что десятичная точка находится перед четвёртой справа цифрой). Или буквально как дроби (целый числитель и целый знаменатель). Эти способы гарантируют абсолютную точность вычислений и, соответственно, никаких внезапных двоек в конце не будет появляться. (Конечно, работать это будет медленее и памяти кушать больше, но это если хочется точное значение. Именно для «быстро прикинуть» и есть арифметика с плавающей точкой.)

Вот только эти вкусняшки забыли встроить в C++ на уровне языка.
kravam
быдлокодер
 Аватар для kravam
1512 / 872 / 44
Регистрация: 04.06.2008
Сообщений: 5,271
28.07.2012, 21:03     Компилятор сломался или ошибка? #31
Цитата Сообщение от ~OhMyGodSoLong~ Посмотреть сообщение
это когда 0,4056 так и хранится как целое число 4056 с припиской, что десятичная точка находится перед четвёртой справа цифрой
Вот я подобное и имел ввиду.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
28.07.2012, 23:09     Компилятор сломался или ошибка?
Еще ссылки по теме:

C++ Или я дурак, или компилятор смеется, вот только чувствую я дурак)
C++ Компилятор Visual C++ платный или нет
C++ Компилятор не дает написать main без int. Ошибка в книге, или в С так можно?

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16825 / 5246 / 321
Регистрация: 30.03.2009
Сообщений: 14,127
Записей в блоге: 26
28.07.2012, 23:09     Компилятор сломался или ошибка? #32
Цитата Сообщение от kravam Посмотреть сообщение
Согласись, это не округление?
Это именно округление, но производится оно в двоичном представлении. Ты бы лучше взял и разобрался, как плавающие числа в двоичном представлении хранятся. Потому что не имея даже приблизительного понимания этого, такие вещи тебе будут казаться дикостью

Цитата Сообщение от ~OhMyGodSoLong~ Посмотреть сообщение
Вот только эти вкусняшки забыли встроить в C++ на уровне языка
На уровень языка такое не встраивают (только в специализированные языки). Максимум - библиотечная поддержка, включённая в стандарт
Yandex
Объявления
28.07.2012, 23:09     Компилятор сломался или ошибка?
Ответ Создать тему
Опции темы

Текущее время: 19:31. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru