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 相关文章推荐
改进的IP计数器
Oct 09 PHP
PHP 加密与解密的斗争
Apr 17 PHP
8个出色的WordPress SEO插件收集
Feb 26 PHP
php导入csv文件碰到乱码问题的解决方法
Feb 10 PHP
php不使用插件导出excel的简单方法
Mar 04 PHP
php实现zip压缩文件解压缩代码分享(简单易懂)
May 10 PHP
php将12小时制转换成24小时制的方法
Mar 31 PHP
PHP JSON格式的中文显示问题解决方法
Apr 09 PHP
php简单复制文件的方法
May 09 PHP
php 广告点击统计代码(php+mysql)
Feb 21 PHP
Centos7安装swoole扩展操作示例
Mar 26 PHP
关于PhpStorm设置点击编辑文件自动定位源文件的实现方式
Dec 30 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 抽象类的简单应用
2011/09/06 PHP
PHP 第二节 数据类型之转换
2012/04/28 PHP
WordPress中创建用户角色的相关PHP函数使用详解
2015/12/25 PHP
Zend Framework实现Zend_View集成Smarty模板系统的方法
2016/03/05 PHP
PHP将字符串首字母大小写转换的实例
2017/01/21 PHP
IE和Mozilla的兼容性汇总event
2007/08/12 Javascript
js活用事件触发对象动作
2008/08/10 Javascript
Dojo 学习笔记入门篇 First Dojo Example
2009/11/15 Javascript
IE6中使用position导致页面变形的解决方案(js代码)
2011/01/09 Javascript
js全屏显示显示代码的三种方法
2013/11/11 Javascript
JQuery日历插件My97DatePicker日期范围限制
2016/01/20 Javascript
JavaScript 输出显示内容(document.write、alert、innerHTML、console.log)
2016/12/14 Javascript
vue router2.0二级路由的简单使用
2017/07/05 Javascript
使用JS实现气泡跟随鼠标移动的动画效果
2017/09/16 Javascript
vue 父组件给子组件传值子组件给父组件传值的实例代码
2019/04/15 Javascript
微信公众号服务器验证Token步骤图解
2019/12/30 Javascript
js实现双人五子棋小游戏
2020/05/28 Javascript
[01:19]2014DOTA2国际邀请赛 采访TITAN战队ohaiyo 能赢DK很幸运
2014/07/12 DOTA
解决谷歌搜索技术文章时打不开网页问题的python脚本
2013/02/10 Python
Python中使用HTMLParser解析html实例
2015/02/08 Python
python使用正则表达式提取网页URL的方法
2015/05/26 Python
widows下安装pycurl并利用pycurl请求https地址的方法
2018/10/15 Python
python区分不同数据类型的方法
2019/10/14 Python
python 画图 图例自由定义方式
2020/04/17 Python
python tqdm实现进度条的示例代码
2020/11/10 Python
python 基于pygame实现俄罗斯方块
2021/03/02 Python
幼儿园中班教师寄语
2014/04/03 职场文书
体育口号大全
2014/06/18 职场文书
党的群众路线教育实践活动个人整改措施落实情况
2014/11/04 职场文书
2014年幼儿园工作总结
2014/11/10 职场文书
社区党支部公开承诺书
2015/04/29 职场文书
小学德育工作总结2015
2015/05/12 职场文书
运动会开幕式主持词
2015/07/01 职场文书
初三英语教学反思
2016/02/15 职场文书
Python Django获取URL中的数据详解
2021/11/01 Python
Windows server 2012搭建FTP服务器
2022/04/29 Servers