Как раскидать сумму пропорционально?

  • автор:

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

Распределение скидки на заказ

Допустим, интернет-магазин предоставляет клиенту скидку на заказ (по бонусной карте, купону или ещё как-то). Скидка даётся на весь заказ, но её действие нужно пропорционально распределить по товарам, чтобы, например, правильно сформировать кассовый чек (в нём каждую позицию чека нужно расписать: цена до применения скидки и сумма с учётом скидки). Пример:
1) Товар №1 — цена 1500 руб.
2) Товар №2 — цена 1700 руб.
Итого сумма заказа получается 3200 руб. Допустим, клиенту предоставляется скидка 10%. В данном случае легко посчитать, что скидка в процентах будет одинаковой для каждого товара:
1) Товар №1 — цена со скидкой 1500 — 10% = 1350 руб.
2) Товар №2 — цена со скидкой 1700 — 10% = 1530 руб.
Это лёгкий пример, где никакого распределения не потребовалось. Теперь изменим условия примера. Допустим, скидка на заказ предоставляется не в процентах, а в рублях, — например, 500 руб. Как её учесть в стоимостях товаров? Вот тут уже требуется распределение. Да, можно было бы скидку целиком вписать в один какой-то товар — но это было бы некрасиво; да к тому же не универсально, ведь все товары в заказе могли бы стоить меньше, чем сумма скидки — что ж теперь отрицательную цену делать?! Нет конечно.
Итак распределяем. Очевидно, что первый товар стоит дешевле, значит, и скидку не него надо сделать меньше, чем на второй товар. Считаем сумму заказа, а потом долю стоимости каждого товара в заказе. Полученную долю умножаем на скидку на заказ.
1) Товар №1 1500 * 100 / 3200 = 46,875% — такова доля стоимости первого товар в общем заказе
2) Товар №2 1700 * 100 / 3200 = 53,125%. Проверим, что мы не ошиблись в округлении и не потеряли какой-нибудь доли заказа: 46,875 + 53,125 = 100% — всё верно.
Скидка распределяется согласно полученным долям:
1) 500 * 46,875 / 100 = 234,375. Получилось не очень-то красивое число. Во-первых, суммы допустимо указывать с копейками, а копейки — это только сотые доли. А тут получились тысячных. Во-вторых, в интернет магазине вообще могут не захотеть иметь дела с копейками. Требуется округление. Приводим к скидке 234 руб. Т.е. цена товара с учётом скидки равна 1500 — 234 = 1266 руб.
2) 500 * 53,125 / 100 = 265,625 — аналогично математическим округлением получаем 266 руб. Цена с учётом скидки 1200 — 266 = 934 руб.
Проверим, все ли 500 рублей мы вписали в виде отдельных скидок по товарам: 234 + 266 = 500 — всё верно.
Это простой пример, который довольно легко поддаётся алгоритмизации и составлению функции. Подобные функции для различных языков мне попадались в интернете. Но есть тут и подвохи, не будь которых — не было бы и статьи.

Подводные камни

Подвоха два:
1. Округление — не всегда округление скидок по товарам в сумме даёт скидку заказа. С этим авторы многих функций решают путём проверки и учёта разницы в последнем или самом дорогом товаре. Алгоритм моей функции навеян функцией отсюда Распределение суммы прапорционально

2. Не все скидки вообще возможно распределить. Это справедливо, когда требуется учитывать ещё и количество товара — т.е. цена товара никогда не должна быть с точностью больше двух десятичных знаков (т.е. копеек), а в большинстве случаев реальных магазинов — вообще без копеек. Т.е. нужна заданная точность. Невозможно ведь иметь три штуки товара в сумме 1000 руб. — тогда каждый из товаров стоил бы 333,(3) (три в периоде) руб. Вот этот момент вообще нигде не нашёл в сети. Автоматическое распределение алгоритмом, как показан выше вполне может выдать такой неделимый результат. Значит алгоритм требует доработки.

Функция распределения

Мой вариант функции (для языка PHP 7) учитывает количество по каждой позиции и заданную точность. Возвращаемый результат — всегда массив с таким же порядком и количеством элементов, что и входящий массив. А все неразрешимые ситуации генерируют исключение. Таким образом функцию не безопасно использовать обычным образом, — требуется организация перехвата исключения и какая-то реакция на исключительное поведение.
Какие могут быть варианты реакций на неразрешимые распределения? Если распределяется некая скидка по купону, то можно пойти на встречу клиенту и в неразрешимой ситуации накинуть рубль или два к скидке — этого вполне может хватить чтобы подыскать ближайший возможный вариант распределения. Потребуется перебор возможных вариантов.
Если это применение бонусов с личного счёта клиента, то неправильно было бы применить больше, чем есть на счёте — тут наоборот нужно подыскать первый доступный вариант с уменьшением скидки (и не забыть пояснить клиенту, что скидка именно такая по математическим и бухгалтерским причинам).
А если, например, первоначальный взнос клиента по кредиту невозможно равномерно распределить по товарам (для печати чека, например), то тут уже надо запрашивать алгоритм действия у бухгалтерии и руководства.
Варианты всегда есть — нужно просто помнить об исключительных ситуациях и адекватно на них реагировать.
Вот сама функция:
/** * Метод выполняет пропорциональное распределение суммы в соответствии с заданными коэффициентами распределения. * Также может выполняться проверка полного деления суммы коэффициента на его количество. Например, * при нулевой точности для чётного количества штук товара было неправильно получить нечётную сумму * после распределения, — правильно немного увеличить сумму распределения (в ущерб пропорциональности), * чтобы добиться ровного распределения по количеству. * Используется, например, при распределении скидки равномерно по позициям корзины. * @param float $sum Распределяемая сумма * @param array $arCoefficients Массив коэффициентов распределения, где ключи — определённые значения, * которые также будут возвращены в виде ключей результирующего массива. Значения — массив с ключами: * «sum» — величина коэффициента (сумма, а не цена) * «count» — количество для коэффициента * @param int $precision Точность округления при распределении. Если передать 0, * то все суммы после распределения будут целыми числами * @throws Exception Выбрасывается исключение в случае, * если невозможно ровно распределить по заданным параметрам * @return array Массив, где сохранены ключи исходного массива $arCoefficients, а значения — массив с ключами: * «init» — начальная сумма, равная соответствующему входному коэффициенту * «final» — сумма после распределения */ public static function getProportionalSums(float $sum, array $arCoefficients, int $precision) : array { $arResult = ; /** * @var float Сумма значений всех коэффициентов */ $sumCoefficients = 0.0; /** * @var float Значение максимального коэффициента по модулю */ $maxCoefficient = 0.0; /** * @var mixed Ключ массива для максимального коэффициента по модулю */ $maxCoefficientKey = null; /** * @var float Распределённая сумма */ $allocatedAmount = 0; foreach ($arCoefficients as $keyCoefficient => $coefficient) { if (is_null($maxCoefficientKey)) { $maxCoefficientKey = $keyCoefficient; } $absCoefficient = abs($coefficient); if ($maxCoefficient < $absCoefficient) { $maxCoefficient = $absCoefficient; $maxCoefficientKey = $keyCoefficient; } $sumCoefficients += $coefficient; } if (!empty($sumCoefficients)) { /** * @var float Шаг, который прибавляем в попытках распределить сумму с учётом количества */ $addStep = (0 === $precision) ? 1 : (1 / pow(10, $precision)); foreach ($arCoefficients as $keyCoefficient => $coefficient) { /** * @var boolean Флаг, удалось ли подобрать сумму распределения для текущего коэффициента */ $isOk = false; /** * @var integer Количество попыток подобрать сумму распределения */ $i = 0; // Далее вычисляем сумму распределения с учётом заданного количества do { $result = round(($sum * $coefficient / $sumCoefficients), $precision) + $i * $addStep; // Проверим распределённую сумму коэффициента относительно его количества if (isset($coefficient) && $coefficient > 0) { if (round($result / $coefficient, $precision) != ($result / $coefficient)) { // Не прошли проверку по количеству — ровно по заданному количеству не распределяется } else { $isOk = true; } } else { // Количество не задано, значит не проверяем распределение по количеству $isOk = true; } $i++; if ($i > 100) { // Мы старались долго. Пора признать, что ничего не выйдет throw new Exception( ‘Не удалось распределить сумму для коэффициента ‘ . $keyCoefficient ); } } while (!$isOk); // Если сюда дошли, значит удалось вычислить сумму распределения $arResult = , ‘final’ => (0 === $precision) ? intval($result) : $result, ‘count’ => $coefficient ]; $allocatedAmount += $result; } if ($allocatedAmount != $sum) { // Есть погрешности округления, которые надо куда-то впихнуть $tmpRes = $arResult + $sum — $allocatedAmount; if (!isset($arResult) || (isset($arResult) && 1 === $arResult) || (isset($arResult) && $arResult > 0 && (round($tmpRes / $arResult, $precision) == ($tmpRes / $arResult)) ) ) { // Погрешности округления отнесём на коэффициент с максимальным весом $arResult = (0 === $precision) ? intval($tmpRes) : $tmpRes; } else { // Погрешности округления нельзя отнести на коэффициент с максимальным весом // Надо подыскать другой коэффициент $isOk = false; foreach ($arCoefficients as $keyCoefficient => $coefficient) { if ($keyCoefficient != $maxCoefficientKey) { // Пробуем погрешность округления впихнуть в текущий коэффициент $tmpRes = $arResult + $sum — $allocatedAmount; if (!isset($arResult) || (isset($arResult) && 1 === $arResult) || (isset($arResult) && $arResult > 0 && (round($tmpRes / $arResult, $precision) == ($tmpRes / $arResult)) ) ) { // Погрешности округления отнесём на коэффициент с максимальным весом $arResult = (0 === $precision) ? intval($tmpRes) : $tmpRes; $isOk = true; break; } } } if (!$isOk) { throw new Exception(‘Не удалось распределить погрешность округления’); } } } } return $arResult; } Проверим на тестовых значениях:

Пример, где всё распределяется без дробной части:

$arProduct = , ]; $arResult = getProportionalSums(1000, $arProduct, 0); echo ‘<pre>’; print_r($arResult); echo ‘</pre>’; Результат:

Расчет транспортных расходов на единицу товара

Добрый день! Подскажите, пожалуйста, как правильно рассчитать транспортные затраты на единицу товара. Я считаю так:
Дано: партия товара
А – 4 шт по 1822 руб
Б – 2 шт по 1496 руб
В – 4 шт по 5814 руб
Г – 2 шт по 1176 руб
Д – 3 шт по 1552 руб
Е – 2 шт по 5614 руб
Ж – 9 шт по 331 руб
Общая сумма поставки — 54751,00 руб.
Стоимость доставки – 3845,00 руб
Вывожу коэффициент затрат: 3845,00/54751 = 0,07022703, который применим к любому товару из этой партии.
Допустим, выписан покупателю счет на А-1шт +В-1шт + Г-1шт + Ж-1шт
После оплаты списываю себестоимость товара по счету — (1822+5814+1176+331) = 9143 руб
и транспортные расходы 9143*0,07022703 = 642,09 руб
Т. о. в КУДиР отражается 9143 руб – списание себестоимости товара и 642,09 руб списание транспортных расходов.
Формула расчета транспортных расходов на единицу товара:
Стоимость доставки / себестоимость партии товара * себестоимость единицы товара.
Такой метод расчетов указан здесь Ссылка удалена модератором. Размещать ссылки и гиперссылки на другие СМИ и коммерческие сайты без согласования с администрацией портала запрещено правилами форума
Увидела в сети и другой способ.
Суммируются затраты на оплату партии товара и транспортные расходы
54751 + 3845 = 58596 руб. Эта сумма принимается за 100%.
Далее считается доля транспортных расходов 3845 / 58596 * 100% = 6,56%
Отсюда если продан товар общей себестоимостью 9143 руб, то транспортные расходы составят 9143/100*6,56 = 599,78 руб.
Такой метод указан здесь: Ссылка удалена модератором. Размещать ссылки и гиперссылки на другие СМИ и коммерческие сайты без согласования с администрацией портала запрещено правилами форума
Эти методы дают разные результаты. Какой из них верен, если транспортные расходы включены в стоимость товара?
Спасибо.

Как посчитать процентное распределение в Excel по формуле

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

Формула процентного распределения в Excel

Как видно ниже на рисунке ниже формула вычисления процентного распределения в Excel очень проста:

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

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



Процентное распределение по динамической формуле Excel

Отдельное вычисление для хранения суммарного дохода в отдельной ячейке как константу – не обязательно. Если мы добавим в формулу функцию =СУММ(), тогда мы можем динамически выполнять вычисление процентного распределения. Ниже на рисунке показано решение для создания динамической формулы процентного распределения отдельных значений.

Примечание: Для тех, кто не в курсе – функция СУММ суммирует все значения, которые заданы в ее аргументах.

Снова обратите внимание на то, что все адреса ссылок, которые заданы в аргументах функции СУММ должны быть абсолютными (в данном случаи). Благодаря зафиксированный абсолютными ссылками диапазон ячеек в аргументе функции СУММ, не изменяться в процессе копирования формулы в другие ячейки.

Пропорциональное деление

  • Деление числа на пропорциональные части
  • Деление на части, обратно пропорциональные числам

Пропорциональное деление – деление какой-нибудь величины на части, прямо или обратно пропорциональные данным числам.

Чтобы разделить число на части пропорционально нескольким данным числам, надо разделить его на сумму этих чисел и частное умножить на каждое из них.

Деление числа на пропорциональные части

Пример 1. Разделить число 50 на части пропорционально числам 2 и 3.

50 : 5 = 10

10 · 2 = 20

10 · 3 = 30

Пример 2. Разделить число 90 на три слагаемых пропорционально числам 1, 2 и 3.

90 : (1 + 2 + 3) = 90 : 6 = 15

1 · 15 = 15

2 · 15 = 30

3 · 15 = 45

Длинные отношения вида 1:2:3 называются сложными. Сложные отношения – это условные записи, показывающие, сколько долей содержит каждая часть. Если члены сложного отношения дробные, то, приведя их к общему знаменателю и умножив на него, можно заменить отношение дробных чисел отношением целых.

Пример. Разделить число 66 на такие три части, чтобы первая относилась ко второй, как 3:2, а вторая к третьей, как 5:4.

a:b = 3:2 = 15:10 значит a:b:c = 15:10:8
b:c = 5:4 = 10:8

a = (66 : 33) · 15 = 30

b = (66 : 33) · 10 = 20

c = (66 : 33) · 8 = 16

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *