Потому что 0.1
не 0,1; это значение не может быть представлено в двойной точности, поэтому оно округляется до ближайшего числа двойной точности, а именно:
0.1000000000000000055511151231257827021181583404541015625
Когда вы звоните fmod
, вы получаете остаток от деления на значение, указанное выше, а именно:
0.0999999999999999500399638918679556809365749359130859375
округляется до 0.1
(или, может быть, 0.09999999999999995
) при печати.
Другими словами, fmod
работает отлично, но вы не даете ему тот вклад, который вы считаете.
Редактировать: Ваша собственная реализация дает вам правильный ответ, потому что он менее точен , хотите верьте, хотите нет. Прежде всего, обратите внимание, что fmod
вычисляет остаток без ошибок округления; единственный источник неточности - ошибка представления, введенная с использованием значения 0.1
. Теперь давайте пройдемся по вашей реализации и посмотрим, как возникшая ошибка округления в точности устраняет ошибку представления.
Оцените a - floor(a/n) * n
один шаг за раз, отслеживая точные значения, вычисленные на каждом этапе:
Сначала мы оцениваем 1.0/n
, где n
является ближайшим приближением двойной точности к 0.1
, как показано выше. Результат этого деления примерно:
9.999999999999999444888487687421760603063276150363492645647081359...
Обратите внимание, что это значение не является представимым числом двойной точности - поэтому оно получает округлено . Чтобы увидеть, как происходит это округление, давайте посмотрим на число в двоичном, а не в десятичном виде:
1001.1111111111111111111111111111111111111111111111111 10110000000...
Пробел указывает, где происходит округление до двойной точности. Так как часть после точки округления больше, чем точная половина пути, это значение округляется до точно 10
.
floor(10.0)
, как и ожидалось, 10.0
. Так что все, что осталось, это вычислить 1.0 - 10.0*0.1
.
В двоичном формате точное значение 10.0 * 0.1
равно:
1.0000000000000000000000000000000000000000000000000000 0100
опять же, это значение не может быть представлено как двойное, и поэтому округляется в позиции, указанной пробелом. На этот раз он округляется до 1.0
, поэтому окончательное вычисление равно 1.0 - 1.0
, что, конечно, 0.0
.
Ваша реализация содержит две ошибки округления, которые в данном случае точно устраняют ошибку представления значения 0.1
. fmod
, напротив, является всегда точным (по крайней мере, на платформах с хорошей библиотекой чисел) и предоставляет ошибку представления 0.1
.