Дробный подсчет через целые числа - PullRequest
6 голосов
/ 09 февраля 2010

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

Например, я получаю целое число 50155, что означает 50 и 15,5 / 32 доллара. Затем я получаю 10210, что составляет 10 и 21/32 долларов. Так 50 15,5 / 32 + 10 21/32 = 61 4,5 / 32, таким образом:

50155 + 10210 = 61045

Опять же, я хочу избежать этого:

int a = 50155;
int b = a / 1000;
float c = a % 1000;
float d = b;
d += c / 320f;
// d = 50.484375

Я бы предпочел это:

int a = 50155;
int b = 10210;
int c = MyClass.Add(a.b); // c = 61045
...
public int Add(int a, int b)
{
    // ?????
}

Заранее спасибо за помощь!

Ответы [ 7 ]

4 голосов
/ 09 февраля 2010

Ну, я не думаю, что вам нужно использовать число с плавающей запятой ...

public static int Add(int a, int b)
{
    int firstWhole = a / 1000;
    int secondWhole = b / 1000;
    int firstFraction = a % 1000; 
    int secondFraction = b % 1000;
    int totalFraction = firstFraction + secondFraction;
    int totalWhole = firstWhole + secondWhole + (totalFraction / 320);
    return totalWhole * 1000 + (totalFraction % 320);
}

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

РЕДАКТИРОВАТЬ: Если вы вынуждены придерживаться формата «одно целое», но можете немного его откорректировать, вы можете рассмотреть возможность использования 512 вместо 1000. Таким образом, вы можете использовать простую маску и сдвиг:

public static int Add(int a, int b)
{
    int firstWhole = a >> 9;
    int secondWhole = b >> 9;
    int firstFraction = a & 0x1ff
    int secondFraction = b & 0x1ff;
    int totalFraction = firstFraction + secondFraction;
    int totalWhole = firstWhole + secondWhole + (totalFraction / 320);
    return (totalWhole << 9) + (totalFraction % 320);
}

Там все еще возиться с 320, но, по крайней мере, несколько лучше.

3 голосов
/ 09 февраля 2010

Редактировать
Этот ответ предполагает, что человек "держится подальше" от арифметики с плавающей точкой. Удивительно, но ОП указал, что его логика на основе числа с плавающей запятой (не показанная по частным причинам) была в два раза быстрее, чем приведенное ниже целочисленное по модулю решение! Приходит, чтобы показать, что FPU не так уж и плохи ...

Определенно, держитесь подальше от поплавков (для этой конкретной проблемы). Целочисленная арифметика более эффективна и не приводит к ошибкам округления.

Что-то вроде следующего должно сработать
Примечание. Как написано, предполагается, что A и B. положительные.

int AddMyOddlyEncodedDollars (int A, int B) {
  int sum;
  sum = A + B
  if (sum % 1000 < 320);
     return sum
  else
     return sum + 1000 - 320;
}

Редактировать : об эффективности оператора по модулю в C
Я очень сильно зависит от компилятора ... Поскольку значение по модулю известно во время компиляции, я ожидаю, что большинство современных компиляторов будут использовать подход «умножение [по взаимному] и сдвиг», и это быстро.
Это беспокойство по поводу производительности (с этим довольно надуманным форматом) требует преждевременной оптимизации, но, опять же, я видел программное обеспечение в финансовой индустрии, которое было сильно оптимизировано (мягко говоря) и вполне оправданно.

3 голосов
/ 09 февраля 2010

Разбейте строку на части, представляющие целые доллары, и части, представляющие дробные части долларов. Что касается последнего, то вместо того, чтобы рассматривать его как 10,5 тридцать секунд доллара, вероятно, его проще трактовать как 105 триста двадцатых доллара (т. Е. Умножить оба на десять, чтобы числитель всегда был целым числом).

Оттуда делать математику довольно просто (если несколько утомительно писать): добавить дроби. Если это превышает целый доллар, несите доллар (и вычтите 320 из дробной части). Затем добавьте целые доллары. Вычитание также - хотя в этом случае вам нужно учитывать заимствование, а не переносить.

2 голосов
/ 09 февраля 2010

В качестве точки для обучения это представление называется " фиксированная точка ". Есть несколько реализаций, которые вы можете посмотреть. Я настоятельно рекомендую вам не использовать int как тип данных верхнего уровня, а вместо этого создать тип с именем Fixed, который инкапсулирует операции. Он будет уменьшать счет вашей ошибки, когда вы по ошибке добавляете обычный int к числу с фиксированной точкой, не масштабируя его первым, или масштабируете число и забыли его масштабировать.

1 голос
/ 09 февраля 2010

Выглядит как странная кодировка для меня.

В любом случае, если формат в 10-значном Nxxx, где N - целое число, обозначающее целые доллары, а xxx интерпретируется как

(ххх / 320)

и вы хотите сложить их вместе, единственное, что вам нужно - это переносить, когда xxx превышает 320:

int a = ..., b = ...; // dollar amounts
int c = (a + b); // add together
// Calculate carry
int carry = (c % 1000) / 320; // integer division
c += carry * 1000;
c -= carry * 320;
// done

Примечание: это работает, потому что, если a и b закодированы правильно, дробные части суммируются не более чем с 638, и, таким образом, нет "переполнения" всей части долларов.

0 голосов
/ 09 февраля 2010

ВНИМАНИЕ : Это сообщение неверно, неправильно , неправильно . Я удалю его, как только перестану чувствовать себя дураком, пытаясь это сделать.

Вот мой путь: Вы можете обменять пространство на время .

Построить отображение для первых 10 битов в кортеж: количество долларов, количество штук 32. Затем используйте битовые манипуляции с вашим целым числом:

  • игнорировать биты 11 и выше, применить карту.
  • сдвиньте целое число 10 раз, добавьте небольшие доллары изменения от отображения выше
  • у вас есть доллар и сумма штук 32
  • добавить оба
    • Перемещение переполнения на сумму в долларах

Далее, чтобы преобразовать обратно в «каноническую» нотацию, вам нужна карта обратного просмотра для ваших кусков32 и «одолжить» доллары, чтобы заполнить биты. Расстегните доллары 10 раз и добавьте кусочки 32.

РЕДАКТИРОВАТЬ: Я должен удалить это, но мне слишком стыдно. Конечно, это не может работать. Я так глуп: (

Причина в том, что смещение на 10 вправо - это то же самое, что деление на 1024 - это не так, как будто некоторые из младших битов имеют сумму в долларах, а некоторые - в единицах 32. Десятичные и двоичные обозначения просто не делятся красиво. Вот почему мы используем шестнадцатеричное обозначение (группа из 4 битов). Облом.

0 голосов
/ 09 февраля 2010

Если вы настаиваете на работе в целых числах, вы не можете решить свою проблему без разбора - ведь ваши данные не являются целочисленными. Я привожу в качестве доказательства (пока) 3 ответа, которые все разбирают ваши целые в их компоненты перед выполнением арифметики.

Альтернативой может быть использование рациональных чисел с 2 (целыми) компонентами, один для всей части и один для числа 320-х в дробной части. Затем реализуйте соответствующую рациональную арифметику. Как всегда, тщательно выбирайте ваши представления данных, и ваши алгоритмы станут намного проще в реализации.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...