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

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

14.09.2025, 12:13. Показов 5124. Ответов 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
-72 / 65 / 2
Регистрация: 23.11.2024
Сообщений: 807
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
1977 / 833 / 115
Регистрация: 01.10.2012
Сообщений: 5,066
Записей в блоге: 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
Нарушитель
622 / 380 / 67
Регистрация: 09.03.2016
Сообщений: 4,172
14.09.2025, 22:34
Хорошая разработка...
Премию вам всем не дадут?
Нобелевскую...
Где float хорошо ипользуеться - На винде... Веь звук float 32 бита..
Вот там точность точно пофигу...
0
1977 / 833 / 115
Регистрация: 01.10.2012
Сообщений: 5,066
Записей в блоге: 2
14.09.2025, 22:35
Цитата Сообщение от ADnD Посмотреть сообщение
Т.е. результат произведения a и b математически точным не будет. В таких случаях double multiply(double a, double b) успешно выбрасывает исключения.
Ну как-то неясно в чем этот успех Обычно требуется довести расчет до конца или "железно" обосновать невозможность
0
Нарушитель
622 / 380 / 67
Регистрация: 09.03.2016
Сообщений: 4,172
14.09.2025, 22:39
0
 Аватар для Storm Screamer
4942 / 1517 / 117
Регистрация: 21.04.2013
Сообщений: 8,963
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
6280 / 3004 / 1051
Регистрация: 01.06.2021
Сообщений: 11,254
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
38207 / 21140 / 4311
Регистрация: 12.02.2012
Сообщений: 34,752
Записей в блоге: 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
359 / 121 / 8
Регистрация: 19.07.2024
Сообщений: 627
15.09.2025, 11:24
Если так страшна эта потеря точности, то может вообще перейти на fixed point в расчётах?
0
1 / 1 / 0
Регистрация: 05.12.2024
Сообщений: 60
15.09.2025, 11:57  [ТС]
Цитата Сообщение от Catstail Посмотреть сообщение
Пользуясь случаем, рекомендую свою (длинную!) лекцию о плавающей точке вот
Видео по ссылке у меня не открывается. Хотя ютуб работает без ограничений (не РФ). Напишите название канала / ваш ник в ютубе / название видео
0
-72 / 65 / 2
Регистрация: 23.11.2024
Сообщений: 807
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
6280 / 3004 / 1051
Регистрация: 01.06.2021
Сообщений: 11,254
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
38207 / 21140 / 4311
Регистрация: 12.02.2012
Сообщений: 34,752
Записей в блоге: 14
15.09.2025, 14:38
ADnD, прошу прощения, ссылки откорректировал.
0
Эксперт функциональных языков программированияЭксперт С++
 Аватар для Royal_X
6280 / 3004 / 1051
Регистрация: 01.06.2021
Сообщений: 11,254
15.09.2025, 15:05
Catstail, прикольная лекция, очень доступно всё объясняете. Порой бывают люди, которые знают много, но другим нормально объяснять не умеют. А у вас прям педагогический талант
1
 Аватар для Storm Screamer
4942 / 1517 / 117
Регистрация: 21.04.2013
Сообщений: 8,963
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
Ответ Создать тему
Новые блоги и статьи
[golang] Конкурентный fetcher с ограничением максимального количества одновременных HTTP запросов.
alhaos 10.06.2026
Задача Реализовать конкурентный fetcher с ограничением максимального количества одновременных HTTP запросов. Сигнатура func Fetch(urls string, maxConcurrent int) Result Пример urls :=. . .
[golang] Состояние гонки (race condition)
alhaos 10.06.2026
Состояние гонки (race condition) Состояние гонки (Race Condition) — это ошибка, возникающая при одновременном доступе нескольких горутин к одним и тем же данным без должной синхронизации. При этом. . .
Взрослые отношения, и почему они не получаются
kumehtar 09.06.2026
Когда в детстве ребёнок не получает от родителей чего-то важного, он лишается не просто приятных переживаний, а основы для формирования определённых внутренних качеств и навыков. Если ребёнок не. . .
[golang] Worker Pool
alhaos 09.06.2026
Worker Pool Worker Pool — паттерн конкурентной обработки задач в Go. Суть: фиксированное количество горутин-воркеров читают задачи из общего канала и пишут результаты в общий канал результатов. . . .
[golang] Pipeline
alhaos 08.06.2026
Pipeline Pipeline — паттерн конкурентной обработки данных в Go. Суть: данные проходят через цепочку независимых стадий, каждая из которых работает в своей горутине и общается с соседями через. . .
Свет внутри себя
kumehtar 07.06.2026
Пусть это будет здесь lIs4oanZS9Y
Программа для com-порта
Uhbif79 05.06.2026
Всем привет, давно хотел изучить Qt, начинал, бросал, потом снова начинал. И сейчас вот смог написать свою первую программу. До этого имел опыт программирования микроконтроллеров, писал прошивки на. . .
Транскрипция 55-минутного видео через Whisper: WhisperDesktop облажался, спас Google Colab[
anaschu 01.06.2026
Понадобилось получить текст из свежезагруженного видео на YouTube. Казалось бы, задача на пять минут. Заняла полтора часа. Делюсь опытом — может кому пригодится последовательность решений. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru