"Почти делимый" - PullRequest
       22

"Почти делимый"

12 голосов
/ 22 марта 2010

Я хочу проверить, является ли значение с плавающей запятой "почти" кратным 32. Например. 64.1 «почти» делится на 32, равно как и 63,9.

Прямо сейчас я делаю это:

#define NEARLY_DIVISIBLE 0.1f
float offset = fmodf( val, 32.0f ) ;
if( offset < NEARLY_DIVISIBLE )
{
    // its near from above
}
// if it was 63.9, then the remainder would be large, so add some then and check again
else if( fmodf( val + 2*NEARLY_DIVISIBLE, 32.0f ) < NEARLY_DIVISIBLE )
{
    // its near from below
}

Есть лучший способ сделать это?

Ответы [ 7 ]

3 голосов
/ 22 марта 2010

ну, вы можете вырезать второй fmodf, просто вычитая 32 еще раз, чтобы получить мод снизу.

  if( offset < NEARLY_DIVISIBLE )
   {
       // it's near from above
   }
   else if( offset-32.0f>-1*NEARLY_DIVISIBLE)
   {
       // it's near from below
   }
2 голосов
/ 22 марта 2010

Вы упоминаете, что вы должны проверить почти делимость с 32 . Следующая теория должна быть верна для проверки почти делимости на степени два:

#define THRESHOLD 0.11
int nearly_divisible(float f) {
    // printf("    %f\n", (a - (float)((long) a)));
    register long l1, l2;
    l1 = (long) (f + THRESHOLD);
    l2 = (long) f;
    return !(l1 & 31) && (l2 & 31 ? 1 : f - (float) l2 <= THRESHOLD);
}

То, что мы делаем, - это принуждаем поплавок, и плаваем + ПОРОГ в длинную позицию.

f       (long) f    (long) (f + THRESHOLD)
63.9    63          64
64      64          64
64.1    64          64

Теперь мы проверяем, делится ли (long) f на 32. Просто проверьте младшие пять битов, если все они установлены в ноль, число делится на 32. Это приводит к серии ложных срабатываний: от 64,2 до 64,8 , при преобразовании в long, тоже 64, и пройдет первый тест. Таким образом, мы проверяем, является ли разница между их усеченной формой и f меньше или равна THRESHOLD.

У этого тоже есть проблема: f - (float) l2 <= THRESHOLD будет верным для 64 и 64,1, но не для 63,9. Таким образом, мы добавляем исключение для чисел меньше 64 (которое при увеличении на THRESHOLD и последующем приведении к длинному - обратите внимание, что обсуждаемый тест должен включать в себя первый тест - делится на 32), указав, что младшие 5 битов не равны нулю. Это будет справедливо для 63 (1000000 - 1 == 1 <strong>11111 ).

Комбинация этих трех тестов будет указывать, делится ли число на 32 или нет. Я надеюсь, что это понятно, пожалуйста, прости мой странный английский.

Я только что проверил расширяемость на другие степени трех - следующая программа печатает числа между 383,5 и 388,4, которые делятся на 128.

#include <stdio.h>

#define THRESHOLD 0.11

int main(void) {
    int nearly_divisible(float);
    int i;
    float f = 383.5;
    for (i=0; i<50; i++) {
        printf("%6.1f %s\n", f, (nearly_divisible(f) ? "true" : "false"));
        f += 0.1;
    }
    return 0;
}

int nearly_divisible(float f) {
    // printf("    %f\n", (a - (float)((long) a)));
    register long l1, l2;
    l1 = (long) (f + THRESHOLD);
    l2 = (long) f;
    return !(l1 & 127) && (l2 & 127 ? 1 : f - (float) l2 <= THRESHOLD);
}

Кажется, пока хорошо работает!

2 голосов
/ 22 марта 2010

В стандартной реализации C можно использовать функцию remainder вместо fmod:

#define NEARLY_DIVISIBLE 0.1f
float offset = remainderf(val, 32.0f);
if (fabsf(offset) < NEARLY_DIVISIBLE) {
    // Stuff
}

Если вы работаете на несовместимой платформе (например, MSVC ++), то, к сожалению, remainder недоступен. Я думаю, что в этом случае ответ fastmultiplication вполне разумен.

0 голосов
/ 23 марта 2010

Это без использования fmodf дважды.

int main(void)
{
    #define NEARLY_DIVISIBLE 0.1f
    #define DIVISOR 32.0f
    #define ARRAY_SIZE 4
    double test_var1[ARRAY_SIZE] = {63.9,64.1,65,63.8};
    int i = 54;
    double rest;
    for(i=0;i<ARRAY_SIZE;i++)
    {
        rest = fmod(test_var1[i] ,DIVISOR);
        if(rest < NEARLY_DIVISIBLE)
        {
            printf("Number %f max %f larger than  a factor of the divisor:%f\n",test_var1[i],NEARLY_DIVISIBLE,DIVISOR);
        }
        else if( -(rest-DIVISOR) < NEARLY_DIVISIBLE)
        {
            printf("Number %f max %f less than  a factor of the divisor:%f\n",test_var1[i],NEARLY_DIVISIBLE,DIVISOR);
        }
    }
    return 0;
}
0 голосов
/ 22 марта 2010

Почему бы вам просто не разделить на 32, а затем округлить и взять разницу между округленным числом и фактическим результатом?

Что-то вроде (простите за непроверенный / псевдокод, нет времени на поиск):

#define NEARLY_DIVISIBLE 0.1f
float result = val / 32.0f;
float nearest_int = nearbyintf(result);
float difference = abs(result - nearest_int);
if( difference < NEARLY_DIVISIBLE )
{
    // It's nearly divisible
}

Если вы все еще хотели выполнять проверки сверху и снизу, вы можете удалить абс и проверить, составляет ли разница> 0 или <0. </p>

0 голосов
/ 22 марта 2010

Я думаю, что это правильно:

bool nearlyDivisible(float num,float div){
float f = num % div;
if(f>div/2.0f){
f=f-div;
}
f=f>0?f:0.0f-f;
return f<0.1f;
}
0 голосов
/ 22 марта 2010

Насколько я понимаю, вы хотите определить, является ли число почти делимым другим, верно?

Я бы сделал что-то вроде этого:

#define NEARLY_DIVISIBLE 0.1f 

bool IsNearlyDivisible(float n1, float n2)
{
   float remainder = (fmodf(n1, n2) / n2);
   remainder = remainder < 0f   ? -remainder : remainder;
   remainder = remainder > 0.5f ? 1 - remainder   : remainder;
   return (remainder <= NEARLY_DIVISIBLE);
}
...