Я не эксперт в программировании, и я столкнулся со следующей проблемой.

Мне нужно вычислить по модулю между числами с плавающей запятой A и B. Поэтому я использую fmod((double)A, (double)B). Теоретически, если A кратно B, то результат равен 0,0. Однако из-за цели точности с плавающей запятой A и B не совсем то число, которое я ожидал иметь. Тогда результатом вычисления по модулю будет не 0,0, а что-то другое. Что проблематично.

Пример: A=99999,9, но компилятор интерпретирует его как 99999,898. B=99,9, но компилятор интерпретирует это как 99,900002. Ожидается, что fmod(A,B) будет 0,0, но на самом деле дает 99,9.

Итак, вопрос: как вы используете, чтобы справиться с такой ситуацией?

Спасибо

2
LudoDu31 24 Мар 2020 в 22:09
Вы должны перевести их на double? Используйте fmodf для поплавков
 – 
Grantly
24 Мар 2020 в 22:13
1
 – 
Groo
24 Мар 2020 в 22:14
Кроме того, существует много других существующих потоков на SO. Короткий ответ: в основном не используйте плавающую точку.
 – 
Groo
24 Мар 2020 в 22:15
1
Кроме того: если вам нужно использовать числа с плавающей запятой, если нет веских причин для использования float, всегда используйте double.
 – 
Weather Vane
24 Мар 2020 в 22:22

1 ответ

Проблема в том, что:
A – это не 99999.9, а 99999,8984375 и
B – это не 99.9, а 99,90000152587890625 и
A mod B равно 99,89691162109375

ОП получает правильный ответ на приведенные аргументы.

Нужно использовать разные дополнения.

Разумной альтернативой является преобразование аргументов в масштабированную степень 10, затем округление до целого числа, %, обратно до с плавающей запятой и не- шкала.

Переполнение вызывает беспокойство.

Поскольку OP хочет обрабатывать числа с точностью до 0,1, масштабируйте на 10.

#include <float.h>
#include <stdio.h>

int main(void) {
  float A = 99999.9;
  float B = 99.9;
  printf("%.25f\n", A);
  printf("%.25f\n", B);
  printf("%.25f\n", fmod(A,B));
  long long a = lround(A*10.0);
  long long b = lround(B*10.0);
  long long m = a%b;
  double D = m/10.0;
  printf("D = %.25f\n", D);
  return 0;
}

Выход

99999.8984375000000000000000000
99.9000015258789062500000000
99.8969116210937500000000000
D = 0.0000000000000000000000000

Альтернатива

  long long a = lround(A*10.0);
  long long b = lround(B*10.0);
  long long m = a%b;
  double D = m/10.0;

Масштабируйте, но пропустите часть преобразования integer

  double a = round(A*10.0);
  double b = round(B*10.0);
  double m = fmod(a,b);
  double D = m/10.0;
2
chux - Reinstate Monica 24 Мар 2020 в 23:58
Новые десятичные типы в следующем стандарте C должны обеспечить приемлемое решение этой проблемы.
 – 
chqrlie
24 Мар 2020 в 23:42
1
Я не знаю об эмуляции gcc, но quickjs имеет программную эмуляцию для BigDecimal и BigFloat.
 – 
chqrlie
25 Мар 2020 в 00:29
1
@chqrlieforyellowblockquotes: Нет, не будут. Все арифметические операции с фиксированной точностью имеют ошибки округления: целые числа, числа с фиксированной точкой, двоичные числа с плавающей запятой, десятичные числа с плавающей запятой. Причина, по которой люди теряют голову из-за двоичных чисел с плавающей запятой, имеющих относительно небольшую ошибку округления (например, в операции 7/6, это примерно одна часть из девяти квадриллионов), но не из-за гигантских ошибок в других арифметических операциях (в целочисленной арифметике). , ошибка в 7/6 составляет одну часть из семи!) потому что это незнакомо им и трудно увидеть, используя десятичную арифметику, которой их учили в начальной школе.
 – 
Eric Postpischil
25 Мар 2020 в 02:48
1
@chqrlieforyellowblockquotes: Таким образом, десятичные числа с плавающей запятой, по крайней мере, облегчат людям чтение и запись чисел, но ошибки округления по-прежнему будут присутствовать во всех операциях, кроме преобразования из десятичного числа (с не большим количеством цифр) и в десятичное (с не меньшим количеством цифр). цифры). Разделите на три, вычислите логарифм, оцените годовую процентную ставку за период ипотеки, и ошибки все равно будут. И как только возникает ошибка, ни fmod, ни любая другая разрывная функция не даст результатов, как если бы использовалась математика действительных чисел.
 – 
Eric Postpischil
25 Мар 2020 в 02:50
1
@EricPostpischil: согласен на 100%. Математика с плавающей запятой не идеальна, и использование типов Decimal не решит проблемы точности, т. е. проблемы, возникающие при вычислениях, требующих округления для представления в целевом типе. С ними проблема ОП не будет решена полностью, но классическая задача, где нужно разбить сумму на минимальное количество монет, больше не должна иметь удивительных ошибок.
 – 
chqrlie
25 Мар 2020 в 11:57