f # целочисленный модуль с плавающей запятой 1.0 = 1.0?

Хорошо, у меня есть две функции, первая из которых выглядит так:

let dlth x = float (x.ToString().Length)

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

let droot x = ((x ** (1./(dlth x))) % 1.)

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

поэтому для droot 36 требуется (36.0 ** (1.0 / 2.0)), что составляет 6.0, тогда 6.0 mod 1.0 равно 0.0;

Теперь это отлично работает вплоть до того момента, когда я пробую число 81.0. (и все числа выше 81, которые должны работать), который по какой-то причине возвращает 1.0, отбрасывая мое сопоставление с образцом. Кто-нибудь может сказать мне, почему это происходит?

PostScript: это часть решения Project Euler. Если вы знаете, какая проблема, пожалуйста, НЕ публиковать решение Project Euler. Мне просто нужна помощь, чтобы понять, почему модуль возвращает забавные результаты.


person AvatarOfChronos    schedule 11.08.2009    source источник


Ответы (3)


Арифметика с плавающей точкой сопряжена с опасностями для любого языка. На моей коробке

printfn "%f" (0.9999999999999 % 1.0)        

отпечатки

1.000000

Надеюсь, это поможет направить вас в правильном направлении. Если вы действительно хотите узнать, является ли число с плавающей запятой "целым числом", то, например, вычитание ближайшего целого числа и проверка того, меньше ли абсолютное значение некоторого эпсилона (например, 0,00001), - неплохая ставка.

person Brian    schedule 11.08.2009
comment
Я понимаю, что вы имеете в виду, но я думаю, что с диапазоном чисел, который я проверяю, было бы почти невозможно создать тест, который работал бы для всех. Думаю, что я собираюсь сделать, так это посмотреть, смогу ли я переделать его, чтобы полностью исключить плавающие объекты. - person AvatarOfChronos; 11.08.2009

Если вы получаете неточности с плавающей запятой, вы можете снизить производительность с помощью типа данных .NET decimal (который может представлять все числа, представленные в системе base-10). В остальном используйте более точную (double) или целочисленную математику.

person MighMoS    schedule 11.08.2009

Я согласен с Брайаном в том, что полагаться на точные представления с плавающей запятой опасно, но я не могу воспроизвести вашу проблему. Для меня droot 81.0 дает ожидаемый результат 0.0. Какую версию F # вы используете? Вы используете .NET или Mono?

person kvb    schedule 11.08.2009
comment
Я пробовал на обоих. единственная разница в том, где начинается проблема. в .net он начинает вести себя забавно на 216. на моно он начинает действовать смешно на 81. И моно, и .net имеют одну и ту же проблему: они возвращают неправильный модуль с этим кодом. и я использую F # 1.9.6.16 - person AvatarOfChronos; 11.08.2009
comment
Да, проблема в том, что (216.0 ** (1.0 / 3.0)) дает 5.9999999999999991 ... Затем оператор модуля дает 0,9 ..., который отображается как 1,0. Вы можете продемонстрировать себе, что на самом деле это не 1.0, выполнив простое сравнение на равенство. К сожалению, вам нужно будет придумать более надежный способ вычислить то, что вы хотите. - person kvb; 11.08.2009
comment
Я не вижу, как кубический корень из 216 возвращается как 5,9999999 ... 1. Думаю, мне придется вникнуть в то, как .Net выполняет математику с плавающей запятой, и посмотреть, что я могу узнать об этом. - person AvatarOfChronos; 11.08.2009
comment
Это не то, как .Net выполняет вычисления с плавающей запятой, это математика с плавающей запятой. 1.0 / 3.0 не может быть точно представлен конечным числом двоичных цифр, значение всегда будет немного неправильным. - person Brian; 11.08.2009