PHP切割整数工具类似微信红包金额分配的思路详解


Posted in PHP onSeptember 18, 2019

 Composer地址:https://packagist.org/packages/werbenhu/php-number-slicing

GitHub地址:https://github.com/werbenhu/php-number-slicing

主要代码:NumberSlicing.php

思路:将数字按精度放大倍数,比如切割数字1,切割的份数是10,精度是0.01,则将1放大100 X 10倍,然后再来对加了1000倍权重后的值进行切割。切割完成之后,再将权重去除,保证总值是1。

<?php
namespace Werben\Tools;
use Exception;
class NumberSlicing {
 /**
  * 精确小数点,舍弃最后一位之后的数据(非四舍五入)
  * floor with precision
  * @param $number 要精确的数
  * @param $precision 精度,比如保留到0.01,则该值为2
  * @return float|int
  */
 public static function floorWithPrecision($number, $precision) {
  $power = pow(10, $precision);
  $ret = floor($number * $power) * 1.0 / $power ;
  return $ret;
 }
 /**
  * 精确小数点,按四舍五入保留最后一位
  * round with precision
  * @param $number 要精确的数
  * @param $precision 精度,比如保留到0.01,则该值为2
  * @return float|int
  */
 public static function roundWithPrecision($number, $precision) {
  $power = pow(10, $precision);
  $ret = round($number * $power) * 1.0 / $power ;
  return $ret;
 }
 /**
  * 将数把权重放大,比如1,要按精度0.0001分配,则先将1乘以10000然后再来分配
  * random the sum weights 加上权重之后,整个要切割的数的权重总值
  * @param $weight_items 用来保留,随机分配的权重值
  * @param $count 要切割的份数
  * @param int $each_weight 加上权重之后,每一份平均的权重值
  * @param int $min_weight 加上权重之后,最小额度的值
  * @return float|int
  */
 public static function weightSlicing(&$weight_items, $count, $each_weight = 10, $min_weight = 3)
 {
  $already_count = count($weight_items);
  $cur_random_full_total = ($already_count + 1) * $each_weight;
  $already_random_real_total = 0;
  foreach ($weight_items as $value) {
   $already_random_real_total += $value;
  }
  $cur_random_rest = $cur_random_full_total - $already_random_real_total;
  if ($already_count == $count - 1) {
   $cur_random_rate = $cur_random_rest;
  } else {
   $cur_random_rate_max = $cur_random_rest + $each_weight - $min_weight * 2;
   $cur_random_rate = $min_weight + mt_rand(0, $cur_random_rate_max);
  }
  $weight_items[] = $cur_random_rate;
  return $cur_random_rate;
 }
 /**
  * slicing the number
  * @param int $number
  * @param int $size
  * @param float $precision
  * @param float $min
  * @return array
  * @throws Exception
  */
 public static function numberSlicing($number, $size, $precision = 0.01, $min = 0.01) {
  if ($number * 1.0 / $size <= $min) {
   throw new Exception('min number is bigger than the average value!');
  }
  if ($precision > 1) {
   throw new Exception('precision can\'t bigger than 1!');
  }
  if ($min < $precision) {
   throw new Exception('precision can\'t bigger than min!');
  }
  $weight_items = [];
  $items = [];
  //不加权重情况下,每一份的平均值
  $each_weight = intval($number / $size);
  if ($precision < 1) {
   //如果精度是小数
   if ($each_weight > 1) {
    //如果平均值大于1,则最小额度则直接用min就可以了
    //每一份的平均值乘以权重的值,比如精度为0.01,则每一份的平均值要乘以权重(100)
    $each_weight = intval((1 / $precision) * $number / $size);
    //最小数值也要乘以权重
    $min_weight = intval(1 / $precision) * $min;
   } else {
    //如果平均值小于1,需要将平均值也乘以权重
    $each_weight = intval(1 / $precision);
    $min_weight = $each_weight * $size * $min / $number;
   }
   $precision_num = log10(1 / $precision);
  } else {
   //如果精度是整数(1)
   $min_weight = $min;
   $precision_num = 0;
  }
  $sum_item_number = 0.0;
  $sum_weight = 0.0;
  //先将整个数,随机按最小额度分配
  for ($i = 0; $i < $size; $i++) {
   $cur_weight = self::weightSlicing($weight_items, $size, $each_weight, $min_weight);
   //将权重去除,换算回原先的比例
   $rate = ($number * $cur_weight * 1.00) / ($size * $each_weight);
   $rate = self::floorWithPrecision($rate, $precision_num);
   $sum_item_number += $rate;
   $sum_weight += $cur_weight;
   $items[] = $rate;
  }
  //由于误差,随机分配后,还会遗留一些数没有完全分配完,则将剩下的数随机分配
  if ($precision_num != 0) {
   //如果是切割成小数
   $rest = $number - $sum_item_number;
   while ($rest - 0.00 > PHP_FLOAT_MIN) {
    if ($rest / $min >= 1.0) {
     //剩余的数大于min最小额度,则将每份最小额度随机分配
     $random_index = mt_rand(0, $size - 1);
     $items[$random_index] = self::roundWithPrecision($items[$random_index] + $min, $precision_num);
     $sum_item_number = self::roundWithPrecision($sum_item_number + $min, $precision_num);
     $rest = self::roundWithPrecision($number - $sum_item_number, $precision_num);
    } else {
     //剩余的数小于min最小额度,则将这最后的未分配的数随机分配
     $random_index = mt_rand(0, $size - 1);
     $items[$random_index] = self::roundWithPrecision($items[$random_index] + $number - $sum_item_number, $precision_num);
     $sum_item_number = $number;
     $rest = $number - $sum_item_number;
    }
   }
  } else {
   //如果是切割成整数
   $rest = $number - $sum_item_number;
   while ($rest > 0) {
    if ($rest / $min >= 1) {
     $random_index = mt_rand(0, $size - 1);
     $items[$random_index] += $min;
     $sum_item_number += $min;
     $rest = $number - $sum_item_number;
    } else {
     $random_index = mt_rand(0, $size - 1);
     $items[$random_index] += $rest;
     $sum_item_number += $rest;
     $rest = $number - $sum_item_number;
    }
   }
  }
  return $items;
 }
}

测试代码:

use Werben\Tools\NumberSlicing;
 
function testIntSlicing2IntOne() {
 $precision = 1; //精确度 eg: 1, 0.1, 0.01, 0.01
 $size = 10;   //切割的份数,the size of the number to slicing
 $min = 3;  //最小额度,最小额度必须大于最小精度,min amount eg: 3, 0.23, 0.05, 0.008
 $number = 100;  //要切割的数字,the number
 $items = NumberSlicing::numberSlicing($number, $size, $precision, $min);
 $sum = 0.0;
 $ret_min = $number;
 foreach ($items as $value) {
  $sum += $value;
  if ($ret_min > $value) {
   $ret_min = $value;
  }
 }
 $count = count($items);
 echo "count: $count, sum: $sum, ret_min: $ret_min\n";
 echo "items : ". json_encode($items) ."\n";
}
function testIntSlicing2IntTwo() {
 $precision = 1; //精确度 eg: 1, 0.1, 0.01, 0.01
 $size = 30;   //切割的份数,the size of the number to slicing
 $min = 18666;  //最小额度,最小额度必须大于最小精度,min amount eg: 3, 0.23, 0.05, 0.008
 $number = 800000;  //要切割的数字,the number
 $items = NumberSlicing::numberSlicing($number, $size, $precision, $min);
 $sum = 0.0;
 $ret_min = $number;
 foreach ($items as $value) {
  $sum += $value;
  if ($ret_min > $value) {
   $ret_min = $value;
  }
 }
 $count = count($items);
 echo "count: $count, sum: $sum, ret_min: $ret_min\n";
 echo "items : ". json_encode($items) ."\n";
}
function testIntSlicing2FloatOne() {
 $precision = 0.01; //精确度 eg: 1, 0.1, 0.01, 0.01
 $size = 1000;   //切割的份数,the size of the number to slicing
 $min = 0.05;  //最小额度,最小额度必须大于最小精度,min amount eg: 3, 0.23, 0.05, 0.008
 $number = 100;  //要切割的数字,the number
 $items = NumberSlicing::numberSlicing($number, $size, $precision, $min);
 $sum = 0.0;
 $ret_min = $number;
 foreach ($items as $key => $value) {
  $sum += $value;
  if ($ret_min > $value) {
   $ret_min = $value;
  }
 }
 $count = count($items);
 echo "count: $count, sum: $sum, ret_min: $ret_min\n";
 echo "items: ". json_encode($items) ."\n";
}
function testIntSlicing2FloatTwo() {
 $precision = 0.00001; //精确度 eg: 1, 0.1, 0.01, 0.01
 $size = 1000;   //切割的份数,the size of the number to slicing
 $min = 0.00005;  //最小额度,最小额度必须大于最小精度,min amount eg: 3, 0.23, 0.05, 0.008
 $number = 5;  //要切割的数字,the number
 $items = NumberSlicing::numberSlicing($number, $size, $precision, $min);
 $sum = 0.0;
 $ret_min = $number;
 foreach ($items as $key => $value) {
  $sum += $value;
  if ($ret_min > $value) {
   $ret_min = $value;
  }
 }
 $count = count($items);
 echo "count: $count, sum: $sum, ret_min: $ret_min\n";
 echo "items: ". json_encode($items) ."\n";
}

总结

以上所述是小编给大家介绍的PHP切割整数工具类似微信红包金额分配的思路详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

PHP 相关文章推荐
非常好的php目录导航文件代码
Oct 09 PHP
解决GD中文乱码问题
Feb 14 PHP
zf框架的数据库追踪器使用示例
Mar 13 PHP
php使用PDO操作MySQL数据库实例
Dec 30 PHP
PHP简单处理表单输入的特殊字符的方法
Feb 03 PHP
关于PHP内置的字符串处理函数详解
Feb 04 PHP
php使用curl实现简单模拟提交表单功能
May 15 PHP
PHP反射学习入门示例
Jun 14 PHP
PHP Trait代码复用类与多继承实现方法详解
Jun 17 PHP
php实现网页上一页下一页翻页过程详解
Jun 28 PHP
在laravel中实现将查询的对象转换为多维数组的函数
Oct 21 PHP
Laravel手动返回错误码示例
Oct 22 PHP
php实现多站点共用session实现单点登录的方法详解
Sep 18 #PHP
PHP实现批量修改文件名的方法示例
Sep 18 #PHP
php DES加密算法实例分析
Sep 18 #PHP
php实现QQ小程序发送模板消息功能
Sep 18 #PHP
php文件后缀不强制为.php的实操方法
Sep 18 #PHP
php校验公钥是否可用的实例方法
Sep 17 #PHP
php写入mysql中文乱码的实例解决方法
Sep 17 #PHP
You might like
php中实现精确设置session过期时间的方法
2014/07/17 PHP
简单谈谈PHP vs Node.js
2015/07/17 PHP
php代码架构的八点注意事项
2016/01/25 PHP
PHP实现的简单分页类及用法示例
2016/05/06 PHP
[原创]php实现子字符串位置相互对调互换的方法
2016/06/02 PHP
PHP foreach遍历多维数组实现方式
2016/11/16 PHP
jquery图片延迟加载 前端开发技能必备系列
2012/06/18 Javascript
JavaScript实现x秒后自动跳转到一个页面
2013/01/03 Javascript
Node.js巧妙实现Web应用代码热更新
2015/10/22 Javascript
jquery实现的点击翻书效果代码
2015/11/04 Javascript
jquery+css3实现会动的小圆圈效果
2016/01/27 Javascript
jQuery插件easyUI实现通过JS显示Dialog的方法
2016/09/16 Javascript
移动端js图片查看器
2016/11/17 Javascript
详解基于javascript实现的苹果系统底部菜单
2016/12/02 Javascript
Bootstrap显示与隐藏简单实现代码
2017/03/06 Javascript
JS实现的邮箱提示补全效果示例
2018/01/30 Javascript
vue项目中api接口管理总结
2018/04/20 Javascript
javascript触发模拟鼠标点击事件
2019/06/26 Javascript
[02:05]2014DOTA2国际邀请赛 BBC外卡赛赛后总结
2014/07/09 DOTA
R vs. Python 数据分析中谁与争锋?
2017/10/18 Python
python requests爬取高德地图数据的实例
2018/11/10 Python
深入了解Python 变量作用域
2020/07/24 Python
Python变量及数据类型用法原理汇总
2020/08/06 Python
使用python库xlsxwriter库来输出各种xlsx文件的示例
2020/09/01 Python
使用CSS媒体查询(Media Queries)和JavaScript判断浏览器设备类型的方法
2014/04/03 HTML / CSS
详解通过HTML5 Canvas实现图片的平移及旋转变化的方法
2016/03/22 HTML / CSS
英国在线泳装店:Simply Swim
2019/05/05 全球购物
英语系本科生个人求职信
2013/09/21 职场文书
军训自我鉴定
2013/12/14 职场文书
网络技术专业求职信
2014/02/18 职场文书
制药工程专业职业生涯规划范文
2014/03/10 职场文书
工作所在部门证明
2014/09/21 职场文书
2014年大学教师工作总结
2014/12/02 职场文书
四群教育工作总结
2015/08/10 职场文书
导游词之岳阳楼
2019/09/25 职场文书
新手,如何业余时间安排好写作、提高写作能力?
2019/10/21 职场文书