Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.91/11: Рейтинг темы: голосов - 11, средняя оценка - 4.91
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
1

Каков принцип округления дробных чисел у компьютера/компилятора?

25.07.2018, 23:01. Просмотров 2288. Ответов 88
Метки нет (Все метки)

Всем привет.

У меня тут такой вопрос для темы: Как именно осуществляется округление дробных чисел в компьютере? Какими правилами он пользуется?
Я пишу программу, просто как тренировочную, в целях практики, так как я только начинаю путь программирования,ее цель (программы) это тренинг по математике, различного уровня сложности и головоломки, и вот в этой программе, возникли проблемы с расхождением результатов: результат, который ввожу я, (вычисленный на калькуляторе) отличается от результата, который вычисляет компьютер. При том что используемый калькулятор - стандартный в ОС Windows. Мало того значение в переменной округляется на разных этапах компиляции, что приводит в расхождению результатов и невозможно подобрать правильный. Такое ощущение, что компьютер играет со мной в стаканчики и крутит вертит эти результаты, чтобы запутать.

Прогонял через отладчик, и даже просто переписывал результат из переменной хранящей результат, но при сравнении снова выдает ошибку совпадения, результаты разные, компилятор округлил значение на каком то этапе компиляции и уже несовпадение.

В общем вопрос очень тонкий, надеюсь есть решения, которые можно осуществить на уровне начальных знаний о С++, или же надо подключать сложные и громоздкие математические библиотеки, с которыми я еще не знаком?

Конечно программа не принесет прибыли, и даже может пользы (в плане математики) от нее будет не много, но как практическая работа она очень интересная.


P.S. Пытался загрузить проект (правда он не закончен), но не получается пока... буду пытаться еще грузить, может кому интересно будет на него взглянуть.
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
25.07.2018, 23:01
Ответы с готовыми решениями:

Как избежать округления дробных чисел
как сделать что бы не округляло ??? float t; float z,m; using namespace std; void main(void)...

Как избежать округления дробных чисел
не виводит дробь а только целие числа. где проблема ? #include <iostream.h> #include <math.h>...

Каков принцип работы атрибутов
Всем доброго времени суток. Объясните пожалуйста принцип работы атрибутов. То есть я написал...

Каков принцип работы парсера?
Здравствуйте! Можете какой нибудь парсер объяснить? Именно как он работает, можно даже...

88
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
25.07.2018, 23:34  [ТС] 2
Загрузился только .cpp файл, весь проект почему то не грузиться.
0
Вложения
Тип файла: zip MathTrainer.zip (10.7 Кб, 5 просмотров)
2655 / 1831 / 548
Регистрация: 05.06.2014
Сообщений: 5,297
25.07.2018, 23:55 3
Цитата Сообщение от txtbit Посмотреть сообщение
Как именно осуществляется округление дробных чисел в компьютере?
Число приводится в форму мантисса*2^экспонента, где мантисса и экспонента - целые числа. Мантисса имеет ограниченное количество двоичных знаков, те что не влезли - отбрасываются. Десятичные дроби в таком представлении становятся бесконечными, а потому у них знаки будут отбрасываться всегда. Разумеется, чем больше у вас операций дающих слишком много знаков в мантиссе, тем больше этих знаков будет выкинуто в процессе вычислений.

Впрочем, если вы о преобразовании double->int, то там просто обрубается дробная часть.
2
1346 / 986 / 312
Регистрация: 28.07.2012
Сообщений: 2,743
26.07.2018, 00:17 4
txtbit, думаю, что в калькуляторе Windows используется длинная арифметика.
Цитата Сообщение от txtbit Посмотреть сообщение
Как именно осуществляется округление дробных чисел в компьютере?
Если мне память не изменяет, то обычно округление осуществляется до ближайшего представимого в данном формате числа. Однако алгоритм может отличаться в зависимости от платформы. Подробнее можешь глянуть в вики или еще где-нибудь.
1
зомбяк
1409 / 1075 / 308
Регистрация: 14.05.2017
Сообщений: 3,514
26.07.2018, 00:19 5
Цитата Сообщение от txtbit Посмотреть сообщение
которые можно осуществить на уровне начальных знаний о С++
А знания математики на каком примерно уровне? Что такое логарифм уже изучал?
Цитата Сообщение от txtbit Посмотреть сообщение
компилятор округлил значение на каком то этапе компиляции
и калькулятор, и компьютер будут давать ошибку вычисления. "Абсолютно точно" они считают только если речь идёт строго о целых числах, причём если эти числа не получается поместить в типы данных, которые аппаратно обрабатываются процессором, применяется длинная арифметика (https://ru.wikipedia.org/wiki/Длинная_арифметика). Но, конечно, следует учитывать что делить нацело можно только некоторые числа, а остальные делятся с остатком (который либо отбрасывать, либо учитывать в качестве дроби, и т.д., но расчёты со всем этим будут идти очень медленно).

А по поводу чисел с плавающей точкой - да, там степень хранится для 2ки (в двоичной системе процессору легче считать), поэтому они изначально задаются не равными задаваемым десятичным дробям.
1
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
26.07.2018, 11:04  [ТС] 6
Всем спасибо за ответы, но видимо я не совсем точно выразил суть своего вопроса. Слишком мало конкретики в моем изложении сути, поэтому прогнал программу через отладчик и записал изменения всех вычислений на разных этапах, для облегчения более детального и главное визуального восприятия вопроса. Вписывать сюда примеры не стал будет очень много непонятных моментов, поэтому просто вложил файл с таблицей, где наглядно видно в чем заключается суть вопроса этой темы, и надеюсь есть ей решения...

Заранее всем спасибо.
0
Вложения
Тип файла: docx Пример Решений.docx (25.1 Кб, 9 просмотров)
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
26.07.2018, 11:21  [ТС] 7
Цитата Сообщение от Renji Посмотреть сообщение
Впрочем, если вы о преобразовании double->int, то там просто обрубается дробная часть.
Просто преобразовывать double в int конечно же я бы не стал, а если и стал, то подобных вопросов уже не задавал бы, иначе тут и так все понятно, куда деваются дробные знаки)))
0
зомбяк
1409 / 1075 / 308
Регистрация: 14.05.2017
Сообщений: 3,514
26.07.2018, 11:37 8
txtbit, выводишь через cout ? Если да, то укажи сколько тебе требуется дробных знаков с помощью std::setprecision

https://en.cppreference.com/w/... tprecision

Если требуется большая точность, чем у double, то возьми например компилятор minGW, и используй тип long double (https://en.wikipedia.org/wiki/Long_double), т.к. компиляторы от microsoft работают с ним аналогично double.
0
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
26.07.2018, 11:56  [ТС] 9
Цитата Сообщение от TRam_ Посмотреть сообщение
выводишь через cout ?
Да, вывожу через "cout", работаю в Visual Studio 2017.

Цитата Сообщение от TRam_ Посмотреть сообщение
Если да, то укажи сколько тебе требуется дробных знаков с помощью std::setprecision
Ок, спасибо за совет, посмотрю в ближайшее время то это на std::setprecision.

Добавлено через 2 минуты
Сейчас перепроверял свои данные и у меня сомнения, что в первом примере я допустил ошибку в записи чисел, и написал неверное число, что привело в результате к расхождению результатов, но второй вариант точно составлен очень правильно, это проверено.
0
зомбяк
1409 / 1075 / 308
Регистрация: 14.05.2017
Сообщений: 3,514
26.07.2018, 12:04 10
Лучший ответ Сообщение было отмечено txtbit как решение

Решение

Цитата Сообщение от txtbit Посмотреть сообщение
работаю в Visual Studio 2017
В ней есть только тип данных double, типа данных повышенной точности long double в ней нет.

Добавлено через 4 минуты
Цитата Сообщение от txtbit Посмотреть сообщение
но второй вариант точно составлен очень правильно
Если математику знаешь достаточно хорошо, попробуй вручную перевести свои числа в тот вид, в котором они содержатся в double.
https://ru.wikipedia.org/wiki/... й_точности
https://www.cyberforum.ru/blogs/18334/blog88.html
0
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
26.07.2018, 19:56  [ТС] 11
Цитата Сообщение от TRam_ Посмотреть сообщение
https://www.cyberforum.ru/blogs/18334/blog88.html
Данная статья все предельно и понятно изложила. Но возник теперь вопрос, что с этим делать...? Скорее всего будем изучать работу с std::setprecision.
0
зомбяк
1409 / 1075 / 308
Регистрация: 14.05.2017
Сообщений: 3,514
26.07.2018, 20:04 12
Цитата Сообщение от txtbit Посмотреть сообщение
Но возник теперь вопрос, что с этим делать...?
Перевести числа в тот вид, в котором они вычисляются, и определить вручную, какие именно округления происходят в описанных тобою действиях.
Т.е. что будет округлено при переводе из твоих введённых чисел из нормированных по основанию 10 на нормированные с основанием 2, какие остатки будут отброшены при делении полученных чисел в формате double и т.д.

В общем проведи вычисления так, как проводит их процессор. Учитывая что целые значения процессор умеет считать точно, если они не переполняются.
0
Модератор
Эксперт по электронике
8257 / 6118 / 819
Регистрация: 14.02.2011
Сообщений: 21,243
26.07.2018, 20:08 13
txtbit, посмотри вот этот блог
https://www.cyberforum.ru/blogs/18334/blog88.html
Evg все доступно описал

Добавлено через 1 минуту
впрочем TRam_, уже дал на него ссылку

Добавлено через 3 минуты
Цитата Сообщение от txtbit Посмотреть сообщение
Скорее всего будем изучать работу с std::setprecision.
и что она тебе даст?
пойми что в компьютере данные хранятся в двоичном виде а std::setprecision выдает десятичный вид
так если введешь 0.1 то никогда не получишь этого значения поскольку 0.1 бесконечная дробь в двоичном представлении
0
2655 / 1831 / 548
Регистрация: 05.06.2014
Сообщений: 5,297
26.07.2018, 20:11 14
Цитата Сообщение от txtbit Посмотреть сообщение
Данная статья все предельно и понятно изложила. Но возник теперь вопрос, что с этим делать...?
Представлять числа как обыкновенные дроби и натравливать на них длинную арифметику. Правда, тормозить будет будь здоров.
0
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
26.07.2018, 20:44  [ТС] 15
Цитата Сообщение от Renji Посмотреть сообщение
Представлять числа как обыкновенные дроби и натравливать на них длинную арифметику. Правда, тормозить будет будь здоров.
Если честно, я понятия не имею о чем речь идет и как это вообще делать. Если есть литература, которая дает пояснения "на пальцах" с конкретными примерами, то было бы хорошо.

Добавлено через 2 минуты
Цитата Сообщение от ValeryS Посмотреть сообщение
и что она тебе даст?
пойми что в компьютере данные хранятся в двоичном виде а std::setprecision выдает десятичный вид
так если введешь 0.1 то никогда не получишь этого значения поскольку 0.1 бесконечная дробь в двоичном представлени
Возьму на заметку информацию про то что std::setprecision выдает десятичный вид. В будущем может где пригодиться...

Добавлено через 6 минут
Цитата Сообщение от TRam_ Посмотреть сообщение
Перевести числа в тот вид, в котором они вычисляются, и определить вручную, какие именно округления происходят в описанных тобою действиях.
Это поможет мне решить проблему со сравнением результата в переменной, отвечающей за хранение результата, и переменной, в которую пользователь вводит свой результат? Разница в том, что данные не совпадают, и откуда он берет эти данные я не знаю. Скорее всего я туплю по второму кругу, но суть именно в этом. Процессор не все числа записывает в "странную" для меня форму, а некоторые из них.. Как он выбирает что надо так сохранить, а что по другому... и как угадать когда и в каком виде будет сохранено то или иное значение, в этом вопрос, потому что именно это мешает сравнить результаты и завершить программу хеппи эндом.
0
Модератор
Эксперт по электронике
8257 / 6118 / 819
Регистрация: 14.02.2011
Сообщений: 21,243
26.07.2018, 20:49 16
Цитата Сообщение от txtbit Посмотреть сообщение
std::setprecision выдает десятичный вид.
std::setprecision не выдает десятичный вид, он выдает количество отображаемых знаков
вот здесь подробней
setprecision() и setiosflags()
а вот cout,без специальных флагов, выдает десятичный вид
0
-2 / 1 / 0
Регистрация: 18.07.2018
Сообщений: 78
26.07.2018, 20:58  [ТС] 17
Вот пример записи, хранения и вывода данных типа float (использую этот тип, как "тип" переменных), из конкретных примеров во время отладки программы.


-8.40939720е-06 данные хранимые в переменной "ResultLDN", которые вычислил процессор


-8.409397335238171е-6 данные вычисленные калькулятором Windows


8.4094е-06 данные, хранящиеся в переменной "ResultLDN", выведенные в консоль с помощью "std::cout".
0
зомбяк
1409 / 1075 / 308
Регистрация: 14.05.2017
Сообщений: 3,514
26.07.2018, 21:08 18
Выведи
C++
1
std::cout<<setprecision(8)<<ResultLDN;
0
2655 / 1831 / 548
Регистрация: 05.06.2014
Сообщений: 5,297
26.07.2018, 21:09 19
Цитата Сообщение от txtbit Посмотреть сообщение
Если честно, я понятия не имею о чем речь идет и как это вообще делать. Если есть литература, которая дает пояснения "на пальцах" с конкретными примерами, то было бы хорошо.
Обыкновенные дроби и работа с ними - школьный учебник арифметики. Однако, если делать по школьному учебнику, числитель или знаменатель может не влезть в long, даже если сама дробь и небольшая. В этом месте вы должны побить большие числитель и знаменатель на отдельные разряды и умножать/складывать их столбиком. Это собственно длинная арифметика и есть.

Впрочем, если вам хочется поковыряться в матане, то можете заменить столбик на модулярную арифметику. Сразу предупреждаю - деление там является очень нетривиальной операцией (зато, сложение/вычитание/умножение очень быстрое).
0
Evg
Эксперт CАвтор FAQ
21121 / 8137 / 628
Регистрация: 30.03.2009
Сообщений: 22,455
Записей в блоге: 30
26.07.2018, 21:35 20
Лучший ответ Сообщение было отмечено ValeryS как решение

Решение

Чисто на всякий случай. Нужно чётко понимать, что "число" и "запись числа" - это несколько разные вещи

Цитата Сообщение от txtbit Посмотреть сообщение
-8.40939720е-06 данные хранимые в переменной "ResultLDN", которые вычислил процессор
Не так. ResultLDN - это переменная типа float, double, long double или какого-то ещё неведомо какого типа, если он у тебя есть. Внутри себя эта переменная хранит набор нулей и единиц, которые трактуются как некоторое вещественное число, хотя более корректно это нужно рассматривать не как число, а как запись числа в двоичном представлении, упакованном в специальный формат. Разумеется, в общем случае число представлено не точно, т.к. количество бит в типе конечно. Но чем больше бит у типа, тем более точно можно представить число (т.е. тем меньше значение этого числа будет отличаться от оригинала). Другими словами, число хранится в округлённом виде с мантиссой, представленной с точностью до скольких-то знаков после запятой В ДВОИЧНОЙ ЗАПИСИ. Чем больше бит в типе, тем больше число знаков после запятой

Далее при печати это число (запись числа в двоичной форме записи) конвертируется в десятичную форму записи. В общем случае десятичная форма записи будет иметь очень большое число знаков после запятой В ДЕСЯТИЧНОМ ВИДЕ, несмотря на то, что в двоичном виде число знаков после запятой совсем небольшое

В итоге "-8.40939720е-06" - это НЕ данные, хранимые в переменной "ResultLDN". Это их отображение в десятичном представлении, напечатанное с точностью до 8 десятичных знаков мантиссы после запятой

Ну и так, чтобы наглядно всё было перед глазами. Может быть, сообразишь, почему в разных программах на экран печатаются немного разные значения

C++
#include <iostream>
#include <iomanip>
 
// Число 1/3 в двоичной записи имеет бесконечное число
// знаков после запятой. Хранимое в двоичном виде число по сути дела
// округляется до скольких-то двоичных знаков. Чем шире тип, тем больше
// двоичных знаков после запятой, т.е. тем более точно хранимое значение
// будет приближено в реальному значению 1/3, но никогда его не достигнет
float f = 1.0f / 3.0f;
double d = 1.0 / 3.0;
long double ld = 1.0L / 3.0L;
 
int main ()
{
  // Неточно представленное в двоичном представлении число 1/3
  // конвертируется в десятичную запись. При этом в десятичной записи
  // печатаются только первые несколько знаков после запятой. Чем
  // больше знаков печатается, тем ближе напечатанное значение
  // к реально хранимому в переменной значению (которое само по
  // себе НЕ является точным)
  //
  // Можем заметить, что печать как бы одного и того же значения
  // начиная с некоторой точности выглядит по разному для разных
  // типов. Это обусловлено разными потерями точности именно
  // в процессе хранения. Если же мы возьмём число не 1/3, а 1/2,
  // то там таких различий не будет, поскольку 1/2 представляется
  // без потерь в двоичном виде. А при преобразовании в десятичную
  // форму записи имеет маленькое количество десятичных знаков
  // после запятой. Если возьмём число 1/65536, то различия увидим
  // только до нескольких знаков после запятой, а дальше результаты будут
  // совпадать. Это обусловлено тем, что значение в двоичном виде точное,
  // и при преобразовании в десятичную запись имеется сравнительно
  // небольшое количество знаков после запятой
 
  std::cout << std::setprecision (3) << f << std::endl;
  std::cout << std::setprecision (6) << f << std::endl;
  std::cout << std::setprecision (12) << f << std::endl;
  std::cout << std::setprecision (20) << f << std::endl;
  std::cout << std::setprecision (30) << f << std::endl;
 
  std::cout << std::endl;
 
  std::cout << std::setprecision (3) << d << std::endl;
  std::cout << std::setprecision (6) << d << std::endl;
  std::cout << std::setprecision (12) << d << std::endl;
  std::cout << std::setprecision (20) << d << std::endl;
  std::cout << std::setprecision (30) << d << std::endl;
 
  std::cout << std::endl;
 
  std::cout << std::setprecision (3) << ld << std::endl;
  std::cout << std::setprecision (6) << ld << std::endl;
  std::cout << std::setprecision (12) << ld << std::endl;
  std::cout << std::setprecision (20) << ld << std::endl;
  std::cout << std::setprecision (30) << ld << std::endl;
}
Код
0.333
0.333333
0.333333343267
0.3333333432674407959
0.3333333432674407958984375

0.333
0.333333
0.333333333333
0.33333333333333331483
0.333333333333333314829616256247

0.333
0.333333
0.333333333333
0.33333333333333333334
0.333333333333333333342368351437
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
26.07.2018, 21:35

Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь.

Aspnet_regsql: каков принцип работы
Вопрос такого плана - пытаюсь добавить таблички с пользователями с помощью этой утилиты - если...

GZipstream - каков принцип многопоточного сжатия/распаковки?
Добрый день! Интересует тема сходная с...

Каков принцип работы клиент-серверного приложения
Структура прилажения такая: Клиент весит и ждет пока сервер пришлет текст, сервер отсылает...

Каков принцип работы самого простого web crawler?
Здравствуйте! Каков принцип работы самого простого web crawler? В Интернете нашел несколько...


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

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2020, vBulletin Solutions, Inc.