Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
1 / 1 / 0
Регистрация: 05.12.2024
Сообщений: 60

Как обнаружить потерю точности при умножении двух double

14.09.2025, 12:13. Показов 5727. Ответов 66
Метки c++ (Все метки)

Студворк — интернет-сервис помощи студентам
Добрый день,

Пытаюсь разобраться для себя с такими интересными тонкостями вещественной арифметики, как потеря точности и переполнения. Со сложением и вычитанием ясно: если одно из чисел не равно 0, а результат равен второму - значит произошла потеря точности. Но как быть с умножением? Ведь там при потери точности произведение будет равно не одному из множителей, а просто некоему неправильному числу???
0
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
14.09.2025, 12:13
Ответы с готовыми решениями:

[Error] cannot convert 'double (*)(double)' to 'double' for argument '1' to 'double pow(double, double)'
#include <iostream> #include <math.h> using namespace std; int main () { system("cls"); ...

Найти все двузначные числа, которые при умножении на 2 заканчиваются на 8, а при умножении на 3 - на 4.
Привет мозги, нужно решить задачу по Си++. Найти все двузначные числа, которые при умножении на 2...

Ошибка: error LNK2001: unresolved external symbol "double __cdecl Akk(double,double,double)"
#include <iostream> #include <cmath> using namespace std; double Akk(double x, double y, double...

66
 Аватар для SmallEvil
4086 / 2975 / 813
Регистрация: 29.06.2020
Сообщений: 11,000
14.09.2025, 21:03
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от ADnD Посмотреть сообщение
А сравниваться оно будет c 5.23d (тут приведение не выполняется).
Приведение выполняется float к double.
Но в переменной уже сконвертированное неточное значение.
C++
1
2
3
4
5
6
7
8
#include <iostream>
 
int main()
{
    float f = 5.23;
   if (f == 5.23)
     std::cout << "foobar";
}
После прекомпиляции будет прим. след:
C++
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int main()
{
  float f = static_cast<float>(5.2300000000000004);
  if(static_cast<double>(f) == 5.2300000000000004) {
    std::operator<<(std::cout, "foobar");
  } 
  
  return 0;
}
Цитата Сообщение от ADnD Посмотреть сообщение
double multiply(double a, double b)
Это ничего не даст.
0
1 / 1 / 0
Регистрация: 05.12.2024
Сообщений: 60
14.09.2025, 21:13  [ТС]
Цитата Сообщение от SmallEvil Посмотреть сообщение
Приведение выполняется float к double.
Но в переменной уже сконвертированное неточное значение.
Переменная будет приводится к типу литерала? логично, упустил этот момент

Цитата Сообщение от SmallEvil Посмотреть сообщение
Это ничего не даст.
Это просто определение функции, в которой производится умножение с контролем переполнения и потери точности. И она работает.
0
 Аватар для SmallEvil
4086 / 2975 / 813
Регистрация: 29.06.2020
Сообщений: 11,000
14.09.2025, 21:28
Цитата Сообщение от ADnD Посмотреть сообщение
И она работает.
Конечно. Компилируется и выполняется.

Но что она делает? Какое её назначение???

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <limits>
 
double multiply(double a, double b){
   double res = a * b;
   if (res == std::numeric_limits<double>::infinity() || res == -std::numeric_limits<double>::infinity()) 
      throw "overflow";
   if (a && b && (res / a != b || res / b != a)) 
      throw "accloss";
   return res ? res : 0.0;
}
 
int main(){
   double a = 1.124;
   double b = 5.0005; 
 
   if (multiply(a,b) != 5.620562 )
      std::cout << "Ooops!" << std::endl;
}
Чем это помогло?
0
-196 / 65 / 2
Регистрация: 23.11.2024
Сообщений: 852
14.09.2025, 22:13
Цитата Сообщение от Royal_X Посмотреть сообщение
любое число с плавающей запятой
С фиксированной запятой тоже, так же.
0
1 / 1 / 0
Регистрация: 05.12.2024
Сообщений: 60
14.09.2025, 22:27  [ТС]
Цитата Сообщение от SmallEvil Посмотреть сообщение
double a = 1.124;
   double b = 5.0005;
1.124d не равно точно десятичному 1.124, а 5.0005d - не равно точно десятичному 5.0005

Поэтому 1.124d * 5.0005d не равно десятичному 5.620562

Я изучаю как ведут себя числа с плавающей точкой, а не их соответствие тому или иному десятичному числу. Пытаюсь понять, что означает "потеря точности" и как-то ее "пощупать". Пока то что я понял: Если порядок двух операндов сильно различается, то для выражения a * b = c не будет соблюдаться с / a = b. Т.е. результат произведения a и b математически точным не будет. В таких случаях double multiply(double a, double b) успешно выбрасывает исключения. И я наглядно увидел, что числами с плавающей точкой нужно пользоваться осторожно. Могут быть задачи, когда их применение неприемлемо.

Для себя я запомнил это:
Цитата Сообщение от Igor3D Посмотреть сообщение
Все работает с какой-то (ограниченной) точностью, это нормально
Вот и все.
0
1980 / 836 / 115
Регистрация: 01.10.2012
Сообщений: 5,239
Записей в блоге: 2
14.09.2025, 22:30
Цитата Сообщение от ADnD Посмотреть сообщение
Цитата Сообщение от Igor3D Посмотреть сообщение
Сравнивайтесь с epsilon величина которого по смыслу задачи.
Это мне не понятно, признаюсь. Могли бы пояснить?
Примеры
C++
1
2
3
if (value < epsilon) ...
...
if (!(value < MAX_VAL)) ...
Ну и либо подсечь значение до мин/макс либо выбросить исключение. Тупо, но ничего лучшего нет в природе. Понятно что на каждой операции даже это невыносимо, поэтому только в ключевых/контрольных точках.

Не по теме:

Цитата Сообщение от ADnD Посмотреть сообщение
результат такой операции false, т.к. в двоичном представлении числа 5.23f и 5.23d разные, первое 32bit, второе 64bit.
Чуть точнее: сравниваться могут операнды одного типа, здесь это "старший" double. Поэтому левый операнд переводится в double, но он уже не равен "литеральному"

1
 Аватар для Наталья8
640 / 398 / 68
Регистрация: 09.03.2016
Сообщений: 4,404
14.09.2025, 22:34
Хорошая разработка...
Премию вам всем не дадут?
Нобелевскую...
Где float хорошо ипользуеться - На винде... Веь звук float 32 бита..
Вот там точность точно пофигу...
0
1980 / 836 / 115
Регистрация: 01.10.2012
Сообщений: 5,239
Записей в блоге: 2
14.09.2025, 22:35
Цитата Сообщение от ADnD Посмотреть сообщение
Т.е. результат произведения a и b математически точным не будет. В таких случаях double multiply(double a, double b) успешно выбрасывает исключения.
Ну как-то неясно в чем этот успех Обычно требуется довести расчет до конца или "железно" обосновать невозможность
0
 Аватар для Наталья8
640 / 398 / 68
Регистрация: 09.03.2016
Сообщений: 4,404
14.09.2025, 22:39
0
 Аватар для Storm Screamer
5841 / 1599 / 127
Регистрация: 21.04.2013
Сообщений: 9,263
14.09.2025, 22:56
ADnD,

Потеря точности при умножении double возникает, когда произведение требует больше значащих бит, чем позволяет формат IEEE 754 double (53 бита мантиссы). Это приводит к округлению младших разрядов.

Когда вы умножаете два числа double c = a * b, экспоненты складываются: exp_c = exp_a + exp_b, мантиссы перемножаются: mant_c = mant_a * mant_b, получается число до 106 бит. Результат округляется до 53 бит.

Даже умножение 1.1 на 1.1 дает не точный результат:
C++
1
2
3
double x = 1.1;
double y = x * x;
std::cout << std::setprecision(20) << y << "\n"; // выводит: 1.2100000000000001865
Как вариант вычислять относительную ошибку:
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
#include <iostream>
#include <cmath>
#include <iomanip>
 
bool hasPrecisionLoss(double a, double b, double product) {
    if (a == 0 || b == 0) return false; // особый случай
 
    // Вычисляем "идеальный" результат с помощью long double (если поддерживается)
    long double precise = static_cast<long double>(a) * static_cast<long double>(b);
    double precise_double = static_cast<double>(precise);
 
    // Если преобразование long double → double не изменило значение,
    // значит, double мог точно представить результат → потерь нет.
    // Но если есть расхождение — значит, double не смог сохранить всю точность.
 
    if (precise_double == product) {
        // Результат в double совпал с более точным вычислением → возможно, потерь нет
        // Но это ещё не гарантирует — см. ниже!
        return false;
    }
 
    // Теперь сравним с абсолютной погрешностью
    double abs_error = std::abs(product - precise_double);
    double rel_error = abs_error / std::abs(precise_double);
 
    // Порог: считаем, что потеря точности есть, если относительная ошибка > 1 ULP
    // ULP для double ≈ 2^(-52) ≈ 2.22e-16
    const double eps = std::numeric_limits<double>::epsilon(); // ~2.22e-16
    const double threshold = 10 * eps; // допустим 10 ULP — очень консервативно
 
    return rel_error > threshold;
}
 
int main() {
    double a = 1.23456789012345;
    double b = 9.87654321098765;
    double c = a * b;
 
    if (hasPrecisionLoss(a, b, c)) {
        std::cout << "Обнаружена потеря точности!\n";
    } else {
        std::cout << "Точность сохранена.\n";
    }
 
    std::cout << "a = " << std::setprecision(17) << a << "\n";
    std::cout << "b = " << std::setprecision(17) << b << "\n";
    std::cout << "a*b (double) = " << c << "\n";
 
    long double precise = static_cast<long double>(a) * static_cast<long double>(b);
    std::cout << "a*b (long double) = " << std::setprecision(20) << precise << "\n";
 
    return 0;
}
Используя MPFR или Boost.Multiprecision:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <boost/multiprecision/cpp_dec_float.hpp>
using namespace boost::multiprecision;
 
int main() {
    cpp_dec_float_50 a("1.23456789012345");
    cpp_dec_float_50 b("9.87654321098765");
    auto precise = a * b;
 
    double da = static_cast<double>(a);
    double db = static_cast<double>(b);
    double dc = da * db;
 
    cpp_dec_float_50 dc_precise(dc); // преобразуем обратно
    auto error = std::abs(precise - dc_precise);
 
    if (error > 1e-15) {
        std::cout << "Потеря точности: " << error << "\n";
    }
}
Попроще. Если оба числа имеют по 15 значащих цифр, их произведение не может быть точно представлено в double:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <cmath>
#include <cfloat>
 
bool likelyPrecisionLoss(double a, double b) {
    if (a == 0 || b == 0) return false;
 
    // Подсчёт значащих цифр в десятичном представлении
    int digits_a = static_cast<int>(std::floor(std::log10(std::abs(a)))) + 1;
    int digits_b = static_cast<int>(std::floor(std::log10(std::abs(b)))) + 1;
 
    // Если сумма значащих цифр > 16, то потеря точности почти гарантирована
    // (так как double хранит ~15-17 десятичных цифр)
    return digits_a + digits_b > 16;
}
2
Эксперт функциональных языков программированияЭксперт С++
 Аватар для Royal_X
6701 / 3120 / 1064
Регистрация: 01.06.2021
Сообщений: 12,065
14.09.2025, 23:58
Цитата Сообщение от Storm Screamer
чем позволяет формат IEEE 754 double
В С++ даже нет гарантии, что будет соблюдаться этот формат (IEC 559 / IEEE 754).
Нужно сперва проверять. Например, вот проверка для float и double. Пока не выведется true, бессмысленно говорить об этом формате.

C++
1
2
3
4
5
6
7
8
#include <iostream>
#include <limits>
 
int main()
{
    std::cout<< std::numeric_limits<double>::is_iec559 << std::endl;
    std::cout<< std::numeric_limits<float>::is_iec559;
}
Если будет true, то, конечно, эта статья будет актуальной: https://en.wikipedia.org/wiki/... 85#IEC_559
1
Супер-модератор
Эксперт функциональных языков программированияЭксперт Python
 Аватар для Catstail
38228 / 21160 / 4314
Регистрация: 12.02.2012
Сообщений: 34,774
Записей в блоге: 14
15.09.2025, 06:05
Лучший ответ Сообщение было отмечено ADnD как решение

Решение

А что значит, "потеря точности при умножении"? Я с этим не сталкивался. Пусть мантисса имеет m разрядов. При умножении двух чисел сначала перемножатся мантиссы. При этом самое страшное - потеря одного бита (0.1... * 0.1... = 0.01...) а потом сложатся порядки (на точность это никак не отразится).

Пользуясь случаем, рекомендую свою (длинную!) лекцию о плавающей точке вот

или
.
3
1 / 1 / 0
Регистрация: 05.12.2024
Сообщений: 60
15.09.2025, 11:18  [ТС]
Большое спасибо всем ответившим, в особенности Igor3D, Storm Screamer, RoyalX и Catstail. Теперь у меня достаточно материала для изучения и нет необходимости "изобретать велосипед"!

Цитата Сообщение от Storm Screamer Посмотреть сообщение
Используя MPFR или Boost.Multiprecision:
Откомпилировал и погонял первый пример с вычислением относительной ошибки. Появились некие сомнения в его работоспособности:

Кликните здесь для просмотра всего текста
Точность сохранена.
a = -1.7976900000000001e+308
b = 9.9999999999999991e-308
a*b (double) = -17.976900000000001
a*b (long double) = -17.976900000000000546


Решил посмотреть в работе вариант 2 через Boost.Multiprecision. Поставил актуальную версия 1.89.0
Возникла проблема с std::abs(precise - dc_precise). Visual Studio не находит перегруженную функцию для заданного типа аргумента (cpp_dec_float_50???). Какую библиотеку подключить?
0
353 / 151 / 10
Регистрация: 19.07.2024
Сообщений: 867
15.09.2025, 11:24
Если так страшна эта потеря точности, то может вообще перейти на fixed point в расчётах?
0
1 / 1 / 0
Регистрация: 05.12.2024
Сообщений: 60
15.09.2025, 11:57  [ТС]
Цитата Сообщение от Catstail Посмотреть сообщение
Пользуясь случаем, рекомендую свою (длинную!) лекцию о плавающей точке вот
Видео по ссылке у меня не открывается. Хотя ютуб работает без ограничений (не РФ). Напишите название канала / ваш ник в ютубе / название видео
0
-196 / 65 / 2
Регистрация: 23.11.2024
Сообщений: 852
15.09.2025, 14:04
Цитата Сообщение от ADnD Посмотреть сообщение
ютуб работает без ограничений
У меня не работает ютуб, но дополнительно не виден текст ссылки.
он такой:
HTML5
1
https://www.youtube.com/embed/w1XvJnx-ggQ&t=14s
Скачал через telegram-бот по ссылке
HTML5
1
https://www.youtube.com/watch?v=w1XvJnx-ggQ
0
Эксперт функциональных языков программированияЭксперт С++
 Аватар для Royal_X
6701 / 3120 / 1064
Регистрация: 01.06.2021
Сообщений: 12,065
15.09.2025, 14:36
Цитата Сообщение от ADnD Посмотреть сообщение
Видео по ссылке у меня не открывается.
Catstail, просто неправильно интегрировал видео на форуме. Правильно так:

[YOUTUBE]w1XvJnx-ggQ[/YOUTUBE]

Кликните здесь для просмотра всего текста


а если нужно с 14 секунды:

[YOUTUBE=14]w1XvJnx-ggQ[/YOUTUBE]

Кликните здесь для просмотра всего текста


И будет хорошим тоном брать видео в спойлер, чтобы при загрузке страницы не тормозило. Кому надо, тот раскроет спойлер.

Catstail, и на видео у вас опечатка.
На 2:20 вы объясняете, что 1E-8 это 0,000000001, но на самом деле для 1E-8 после запятой нужно 7 нулей, поскольку нотация 1E-8 означает 1*10^(-8). Советую просто перезалить видео, предварительно заменив этот кусок.
1
Супер-модератор
Эксперт функциональных языков программированияЭксперт Python
 Аватар для Catstail
38228 / 21160 / 4314
Регистрация: 12.02.2012
Сообщений: 34,774
Записей в блоге: 14
15.09.2025, 14:38
ADnD, прошу прощения, ссылки откорректировал.
0
Эксперт функциональных языков программированияЭксперт С++
 Аватар для Royal_X
6701 / 3120 / 1064
Регистрация: 01.06.2021
Сообщений: 12,065
15.09.2025, 15:05
Catstail, прикольная лекция, очень доступно всё объясняете. Порой бывают люди, которые знают много, но другим нормально объяснять не умеют. А у вас прям педагогический талант
1
 Аватар для Storm Screamer
5841 / 1599 / 127
Регистрация: 21.04.2013
Сообщений: 9,263
15.09.2025, 15:39
В финансовых расчетах используют тип данных decimal, чтобы избежать потерь. Но в С++, в стандарте по крайней мере, его нет.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
15.09.2025, 15:39

Ошибки error C2296: -: недопустимо, левый операнд имеет тип "double (__cdecl *)(double,double,double
Думаю из-за polp #include&lt;iostream&gt; #include&lt;cmath&gt; #include&lt;cstdlib&gt; using namespace std;...

Создать функцию с параметрами GetFunctionValue(double& a, double& b, double& c, double& x)...
Есть код что считает нужно сделать пару манипуляций что у не могу реализовать 1) создать функцию...

Реализовать в виде GetFunctionValue(double& a, double& b, double& c, double& x)
Реализовать в виде GetFunctionValue(double&amp; a, double&amp; b, double&amp; c, double&amp; x). Задание по...

Реализовать в виде GetFunctionValue(double& a, double& b, double& c, double& x)
Реализовать в виде GetFunctionValue(double&amp; a, double&amp; b, double&amp; c, double&amp; x).

Реализовать в виде GetFunctionValue(double& a, double& b, double& c, double& x)
Реализовать в виде GetFunctionValue(double&amp; a, double&amp; b, double&amp; c, double&amp; x). #include...


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

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Новые блоги и статьи
Вступаем в новую фазу генерации favicons, векторную.
russiannick 02.11.2026
Теперь нет нужды скрывать что используется простой svg. Исходный код изображения помещаем в поле и жмем кнопку. Исходник преобразуется в вид корректный для помещения в favicon или в url(). Нажатие. . .
Генератор паролей (вариант №4)
Maks 01.11.2026
После публикации 3-го варианта генератора паролей, разослал его своим коллегам (с целью оценки), в итоге были получены следующие рекомендации: 1. Вернуть возможность использования знаков | 1 l I;. . .
Генератор паролей (вариант №3)
Maks 30.10.2026
Финальная версия генератора паролей 1С. Доработка решения в этот раз коснулась только визуальной части, а именно, добавлен реквизит ДопПараметры (тип Булево), который скрывает/ отображает реквизиты:. . .
Магическое мышление и магия, современная наука не отличима от магии (говорит ИИ Джемени)
zorxor 30.10.2026
Джемени, расскажи, пожалуйста, что такое магическое мышление, что такое магия, почему современная наука чем-то напоминает магию? Фраза «Любая достаточно развитая технология неотличима от магии». . .
За какое время ИИ может подобрать пароль по хешу (рассказывает Джемени ИИ)
zorxor 30.10.2026
Расскажи, Джемени, если известен хэш МД5, например, за сколько ИИ сможет подобрать соответствующий пароль? Искусственный интеллект (ИИ) может подобрать простой или средний по сложности пароль по. . .
Кто убил Suno? Почему наше солнышко погасло? (диалог с ИИ Джемени)
zorxor 30.10.2026
Как убивали Suno. Хронология. «Убийство» музыкальной нейросети Suno — это затяжной процесс, в котором технологический хайп стартапа разбился о жесткую реальность судебных исков от гигантов. . .
Генератор паролей (вариант №2)
Maks 30.10.2026
Обновленная версия ранее созданной обработки. Добавлены следующие реквизиты типа Булево: - Буквы; - Цифры; - Символы. Изменена логика команды "Сгенерировать" и процедуры "ПриОткрытии":. . .
Генератор паролей (вариант №1)
Maks 29.10.2026
Реализация из решения ниже выполнена исключительно по моей инициативе, дабы облегчить себе работу в создании паролей для администрируемых мною сервисов и пользователей. Самым главным критерием для. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru