Validate check digit 11 (mod 11) using PHP - php

How to validate NHS number using check digit 11 (mod 11) using PHP.
After a long search on the internet trying to find a function that I can use to validate check digit 11 (mod 11) number, I couldn't find so I end up developing one myself and share it here. I hope someone my find it useful.

The code with comments:
###### Check Digit 11 Modulus
function mod11($nhs_no){
//Get Last Number
$checkDigit = (int)substr($nhs_no, -1);
//Get Remaining Numbers
$numbers = substr($nhs_no, 0, -1);
// Sort Numbers Into an Array
$numbers_array = str_split($numbers);
//Define the base for the weights
$base=2;
// Multiply the weights to the numbers
$numbers_array_reversed = array_reverse($numbers_array);
foreach($numbers_array_reversed as $number){$multiplier[$number.'x'.$base]=(float)$number * $base;$base++;}
//Get the total sum
$sum =(float) array_sum($multiplier);
$modulus = 11;
// Divide the sum by check digit 11
$divider = (float)($sum / $modulus);
// Get the remainder
$remainder = (int)(($divider - (int)$divider)*$modulus);
// Check if the remainder is matching with the last check digit
$results = (int) ($modulus-$remainder);
// Return true or false
return ($results == $checkDigit ? true : false);
}

Related

PHP create function Palindrome

I really need your help. I have to create a function that takes 2 positive integers as its arguments and returns the numerical palindromes as an array of n numerical palindromes that come after the num, including num. Also, single-digit numbers are not considered numerical palindromes. So the outcome must be like this --> function(4,6) // returns [11,22,33,44]. Function(4, 6) it's an array that will take only 4 elements and the numerical palindromes must be greater than 6. Other examples are function (1, 75) // returns [77] and function (3, 100) // returns [101, 111, 121]
My code so far:
<?php
function createPalindrome($input)
{
$m = $input;
$palin = $input;
while ($m > 1) {
$d = intval($m % 10);
$palin = $palin * 10 + $d;
$m = intval($m / 10);
}
return $palin;
}
function generatePalindromes($x, $n)
{
$arr = [];
$i = 1;
while (($number = createPalindrome($i)) <= $n) {
$arr[] = $number;
$i++;
}
for($j = 0; $j < $x; $j++)
var_dump($arr[$j]);
}
generatePalindromes(4, 77);
The outcome is:
int(1)
int(22)
int(33)
int(44)
Had to modify this answer a fair bit once Giann49 expounded on his question in a comment reply.
This is not the cleanest or most precise way to do this for sure but it will principally function and hopefully help point you in the right direction logically.
function findExceedingPalindromes($palindromeLimit,$startingPoint){
$palindromesFound = 0; //Set an initial counter for number of palindromes found so far.
$palindromeSet = []; //Create an array to contain all the palindromes.
if($palindromeLimit <= 0 || $startingPoint <= 0){ //Both integers need to be positive as stated.
return false; //If they aren't return false. You can return whatever you want to halt execution of the function. This is just an easy example.
}
if($startingPoint < 10){
$startingPoint = 10; //Since single digits aren't valid if the starting number if less than 10 kick it up to 10.
}
while($palindromesFound <= $palindromeLimit){
$startingPoint++; //Since the first palindrome must exceed the starting point increment it up once at the top of the loop.
$reverseNumber = strrev($startingPoint); //reverse the current number.
if($startingPoint === $reverseNumber){
array_push($palindromeSet,$startingPoint);
$palindomresFound++; //If we find a palindome move the number found 1 higher.
}
}
return $palindromeSet;
}
As an explanation.
The first argument is the number of palindromes to generate. The second argument is the number we want to start palindrome generation at then work up from there.
We create two variables. One is to track how many palindromes have been found. The other is an empty array to insert found palindromes into.
You say the two numbers must be positive integers so if they are anything less than 1 we'll want to exit the function. (optional)
You say single digits don't count so if the starting point is less than 10 we'll just move it up to 10 for starters. (optional)
Now we'll start a while loop. While the number of palindromes is less than the number we want to find the loop will keep running.
We add 1 to the starting point right out of the gate because we want to first palindrome to be higher than the starting point if it already is one. As in if 11 is the number set as the point to start searching we want to look at 11 + 1 for starters. (optional)
To check if a number of a palindrome we want to simply reverse it's string. If the strings are the same forward and back obviously it matches the definition of a palindrome. So we'll add that number into the set of found palindromes and move the number found 1 digit higher.
Once the requested number of palindromes are found we'll break the while loop and return the array of what was found.

How to get number of digits in both right, left sides of a decimal number

I wonder if is there a good way to get the number of digits in right/left side of a decimal number PHP. For example:
12345.789 -> RIGHT SIDE LENGTH IS 3 / LEFT SIDE LENGTH IS 5
I know it is readily attainable by helping string functions and exploding the number. I mean is there a mathematically or programmatically way to perform it better than string manipulations.
Your answers would be greatly appreciated.
Update
The best solution for left side till now was:
$left = floor(log10($x))+1;
but still no sufficient for right side.
Still waiting ...
To get the digits on the left side you can do this:
$left = floor(log10($x))+1;
This uses the base 10 logarithm to get the number of digits.
The right side is harder. A simple approach would look like this, but due to floating point numbers, it would often fail:
$decimal = $x - floor($x);
$right = 0;
while (floor($decimal) != $decimal) {
$right++;
$decimal *= 10; //will bring in floating point 'noise' over time
}
This will loop through multiplying by 10 until there are no digits past the decimal. That is tested with floor($decimal) != $decimal.
However, as Ali points out, giving it the number 155.11 (a hard to represent digit in binary) results in a answer of 14. This is because as the number is stored as something like 155.11000000000001 with the 32 bits of floating precision we have.
So instead, a more robust solution is needed. (PoPoFibo's solutions above is particularly elegant, and uses PHPs inherit float comparison functions well).
The fact is, we can never distinguish between input of 155.11 and 155.11000000000001. We will never know which number was originally given. They will both be represented the same. However, if we define the number of zeroes that we can see in a row before we just decide the decimal is 'done' than we can come up with a solution:
$x = 155.11; //the number we are testing
$LIMIT = 10; //number of zeroes in a row until we say 'enough'
$right = 0; //number of digits we've checked
$empty = 0; //number of zeroes we've seen in a row
while (floor($x) != $x) {
$right++;
$base = floor($x); //so we can see what the next digit is;
$x *= 10;
$base *= 10;
$digit = floor($x) - $base; //the digit we are dealing with
if ($digit == 0) {
$empty += 1;
if ($empty == $LIMIT) {
$right -= $empty; //don't count all those zeroes
break; // exit the loop, we're done
}
} else {
$zeros = 0;
}
}
This should find the solution given the reasonable assumption that 10 zeroes in a row means any other digits just don't matter.
However, I still like PopoFibo's solution better, as without any multiplication, PHPs default comparison functions effectively do the same thing, without the messiness.
I am lost on PHP semantics big time but I guess the following would serve your purpose without the String usage (that is at least how I would do in Java but hopefully cleaner):
Working code here: http://ideone.com/7BnsR3
Non-string solution (only Math)
Left side is resolved hence taking the cue from your question update:
$value = 12343525.34541;
$left = floor(log10($value))+1;
echo($left);
$num = floatval($value);
$right = 0;
while($num != round($num, $right)) {
$right++;
}
echo($right);
Prints
85
8 for the LHS and 5 for the RHS.
Since I'm taking a floatval that would make 155.0 as 0 RHS which I think is valid and can be resolved by String functions.
php > $num = 12345.789;
php > $left = strlen(floor($num));
php > $right = strlen($num - floor($num));
php > echo "$left / $right\n";
5 / 16 <--- 16 digits, huh?
php > $parts = explode('.', $num);
php > var_dump($parts);
array(2) {
[0]=>
string(5) "12345"
[1]=>
string(3) "789"
As you can see, floats aren't the easiest to deal with... Doing it "mathematically" leads to bad results. Doing it by strings works, but makes you feel dirty.
$number = 12345.789;
list($whole, $fraction) = sscanf($number, "%d.%d");
This will always work, even if $number is an integer and you’ll get two real integers returned. Length is best done with strlen() even for integer values. The proposed log10() approach won't work for 10, 100, 1000, … as you might expect.
// 5 - 3
echo strlen($whole) , " - " , strlen($fraction);
If you really, really want to get the length without calling any string function here you go. But it's totally not efficient at all compared to strlen().
/**
* Get integer length.
*
* #param integer $integer
* The integer to count.
* #param boolean $count_zero [optional]
* Whether 0 is to be counted or not, defaults to FALSE.
* #return integer
* The integer's length.
*/
function get_int_length($integer, $count_zero = false) {
// 0 would be 1 in string mode! Highly depends on use case.
if ($count_zero === false && $integer === 0) {
return 0;
}
return floor(log10(abs($integer))) + 1;
}
// 5 - 3
echo get_int_length($whole) , " - " , get_int_length($fraction);
The above will correctly count the result of 1 / 3, but be aware that the precision is important.
$number = 1 / 3;
// Above code outputs
// string : 1 - 10
// math : 0 - 10
$number = bcdiv(1, 3);
// Above code outputs
// string : 1 - 0 <-- oops
// math : 0 - INF <-- 8-)
No problem there.
I would like to apply a simple logic.
<?php
$num=12345.789;
$num_str="".$num; // Converting number to string
$array=explode('.',$num_str); //Explode number (String) with .
echo "Left side length : ".intval(strlen($array[0])); // $array[0] contains left hand side then check the string length
echo "<br>";
if(sizeof($array)>1)
{
echo "Left side length : ".intval(strlen($array[1]));// $array[1] contains left hand check the string length side
}
?>

Can the for loop be eliminated from this piece of PHP code?

I have a range of whole numbers that might or might not have some numbers missing. Is it possible to find the smallest missing number without using a loop structure? If there are no missing numbers, the function should return the maximum value of the range plus one.
This is how I solved it using a for loop:
$range = [0,1,2,3,4,6,7];
// sort just in case the range is not in order
asort($range);
$range = array_values($range);
$first = true;
for ($x = 0; $x < count($range); $x++)
{
// don't check the first element
if ( ! $first )
{
if ( $range[$x - 1] + 1 !== $range[$x])
{
echo $range[$x - 1] + 1;
break;
}
}
// if we're on the last element, there are no missing numbers
if ($x + 1 === count($range))
{
echo $range[$x] + 1;
}
$first = false;
}
Ideally, I'd like to avoid looping completely, as the range can be massive. Any suggestions?
Algo solution
There is a way to check if there is a missing number using an algorithm. It's explained here. Basically if we need to add numbers from 1 to 100. We don't need to calculate by summing them we just need to do the following: (100 * (100 + 1)) / 2. So how is this going to solve our issue ?
We're going to get the first element of the array and the last one. We calculate the sum with this algo. We then use array_sum() to calculate the actual sum. If the results are the same, then there is no missing number. We could then "backtrack" the missing number by substracting the actual sum from the calculated one. This of course only works if there is only one number missing and will fail if there are several missing. So let's put this in code:
$range = range(0,7); // Creating an array
echo check($range) . "\r\n"; // check
unset($range[3]); // unset offset 3
echo check($range); // check
function check($array){
if($array[0] == 0){
unset($array[0]); // get ride of the zero
}
sort($array); // sorting
$first = reset($array); // get the first value
$last = end($array); // get the last value
$sum = ($last * ($first + $last)) / 2; // the algo
$actual_sum = array_sum($array); // the actual sum
if($sum == $actual_sum){
return $last + 1; // no missing number
}else{
return $sum - $actual_sum; // missing number
}
}
Output
8
3
Online demo
If there are several numbers missing, then just use array_map() or something similar to do an internal loop.
Regex solution
Let's take this to a new level and use regex ! I know it's nonsense, and it shouldn't be used in real world application. The goal is to show the true power of regex :)
So first let's make a string out of our range in the following format: I,II,III,IIII for range 1,3.
$range = range(0,7);
if($range[0] === 0){ // get ride of 0
unset($range[0]);
}
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
echo $str;
The output should be something like: I,II,III,IIII,IIIII,IIIIII,IIIIIII.
I've come up with the following regex: ^(?=(I+))(^\1|,\2I|\2I)+$. So what does this mean ?
^ # match begin of string
(?= # positive lookahead, we use this to not "eat" the match
(I+) # match I one or more times and put it in group 1
) # end of lookahead
( # start matching group 2
^\1 # match begin of string followed by what's matched in group 1
| # or
,\2I # match a comma, with what's matched in group 2 (recursive !) and an I
| # or
\2I # match what's matched in group 2 and an I
)+ # repeat one or more times
$ # match end of line
Let's see what's actually happening ....
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^
(I+) do not eat but match I and put it in group 1
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^
^\1 match what was matched in group 1, which means I gets matched
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^ ,\2I match what was matched in group 1 (one I in thise case) and add an I to it
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^^ \2I match what was matched previously in group 2 (,II in this case) and add an I to it
I,II,III,IIII,IIIII,IIIIII,IIIIIII
^^^^^ \2I match what was matched previously in group 2 (,III in this case) and add an I to it
We're moving forward since there is a + sign which means match one or more times,
this is actually a recursive regex.
We put the $ to make sure it's the end of string
If the number of I's don't correspond, then the regex will fail.
See it working and failing. And Let's put it in PHP code:
$range = range(0,7);
if($range[0] === 0){
unset($range[0]);
}
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
if(preg_match('#^(?=(I*))(^\1|,\2I|\2I)+$#', $str)){
echo 'works !';
}else{
echo 'fails !';
}
Now let's take in account to return the number that's missing, we will remove the $ end character to make our regex not fail, and we use group 2 to return the missed number:
$range = range(0,7);
if($range[0] === 0){
unset($range[0]);
}
unset($range[2]); // remove 2
$str = implode(',', array_map(function($val){return str_repeat('I', $val);}, $range));
preg_match('#^(?=(I*))(^\1|,\2I|\2I)+#', $str, $m); // REGEEEEEX !!!
$n = strlen($m[2]); //get the length ie the number
$sum = array_sum($range); // array sum
if($n == $sum){
echo $n + 1; // no missing number
}else{
echo $n - 1; // missing number
}
Online demo
EDIT: NOTE
This question is about performance. Functions like array_diff and array_filter are not magically fast. They can add a huge time penalty. Replacing a loop in your code with a call to array_diff will not magically make things fast, and will probably make things slower. You need to understand how these functions work if you intend to use them to speed up your code.
This answer uses the assumption that no items are duplicated and no invalid elements exist to allow us to use the position of the element to infer its expected value.
This answer is theoretically the fastest possible solution if you start with a sorted list. The solution posted by Jack is theoretically the fastest if sorting is required.
In the series [0,1,2,3,4,...], the n'th element has the value n if no elements before it are missing. So we can spot-check at any point to see if our missing element is before or after the element in question.
So you start by cutting the list in half and checking to see if the item at position x = x
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
Yup, list[4] == 4. So move halfway from your current point the end of the list.
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
Uh-oh, list[6] == 7. So somewhere between our last checkpoint and the current one, one element was missing. Divide the difference in half and check that element:
[ 0 | 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 ]
^
In this case, list[5] == 5
So we're good there. So we take half the distance between our current check and the last one that was abnormal. And oh.. it looks like cell n+1 is one we already checked. We know that list[6]==7 and list[5]==5, so the element number 6 is the one that's missing.
Since each step divides the number of elements to consider in half, you know that your worst-case performance is going to check no more than log2 of the total list size. That is, this is an O(log(n)) solution.
If this whole arrangement looks familiar, It's because you learned it back in your second year of college in a Computer Science class. It's a minor variation on the binary search algorithm--one of the most widely used index schemes in the industry. Indeed this question appears to be a perfectly-contrived application for this searching technique.
You can of course repeat the operation to find additional missing elements, but since you've already tested the values at key elements in the list, you can avoid re-checking most of the list and go straight to the interesting ones left to test.
Also note that this solution assumes a sorted list. If the list isn't sorted then obviously you sort it first. Except, binary searching has some notable properties in common with quicksort. It's quite possible that you can combine the process of sorting with the process of finding the missing element and do both in a single operation, saving yourself some time.
Finally, to sum up the list, that's just a stupid math trick thrown in for good measure. The sum of a list of numbers from 1 to N is just N*(N+1)/2. And if you've already determined that any elements are missing, then obvously just subtract the missing ones.
Technically, you can't really do without the loop (unless you only want to know if there's a missing number). However, you can accomplish this without first sorting the array.
The following algorithm uses O(n) time with O(n) space:
$range = [0, 1, 2, 3, 4, 6, 7];
$N = count($range);
$temp = str_repeat('0', $N); // assume all values are out of place
foreach ($range as $value) {
if ($value < $N) {
$temp[$value] = 1; // value is in the right place
}
}
// count number of leading ones
echo strspn($temp, '1'), PHP_EOL;
It builds an ordered identity map of N entries, marking each value against its position as "1"; in the end all entries must be "1", and the first "0" entry is the smallest value that's missing.
Btw, I'm using a temporary string instead of an array to reduce physical memory requirements.
I honestly don't get why you wouldn't want to use a loop. There's nothing wrong with loops. They're fast, and you simply can't do without them. However, in your case, there is a way to avoid having to write your own loops, using PHP core functions. They do loop over the array, though, but you simply can't avoid that.
Anyway, I gather what you're after, can easily be written in 3 lines:
function highestPlus(array $in)
{
$compare = range(min($in), max($in));
$diff = array_diff($compare, $in);
return empty($diff) ? max($in) +1 : $diff[0];
}
Tested with:
echo highestPlus(range(0,11));//echoes 12
$arr = array(9,3,4,1,2,5);
echo highestPlus($arr);//echoes 6
And now, to shamelessly steal Pé de Leão's answer (but "augment" it to do exactly what you want):
function highestPlus(array $range)
{//an unreadable one-liner... horrid, so don't, but know that you can...
return min(array_diff(range(0, max($range)+1), $range)) ?: max($range) +1;
}
How it works:
$compare = range(min($in), max($in));//range(lowest value in array, highest value in array)
$diff = array_diff($compare, $in);//get all values present in $compare, that aren't in $in
return empty($diff) ? max($in) +1 : $diff[0];
//-------------------------------------------------
// read as:
if (empty($diff))
{//every number in min-max range was found in $in, return highest value +1
return max($in) + 1;
}
//there were numbers in min-max range, not present in $in, return first missing number:
return $diff[0];
That's it, really.
Of course, if the supplied array might contain null or falsy values, or even strings, and duplicate values, it might be useful to "clean" the input a bit:
function highestPlus(array $in)
{
$clean = array_filter(
$in,
'is_numeric'//or even is_int
);
$compare = range(min($clean), max($clean));
$diff = array_diff($compare, $clean);//duplicates aren't an issue here
return empty($diff) ? max($clean) + 1; $diff[0];
}
Useful links:
The array_diff man page
The max and min functions
Good Ol' range, of course...
The array_filter function
The array_map function might be worth a look
Just as array_sum might be
$range = array(0,1,2,3,4,6,7);
// sort just in case the range is not in order
asort($range);
$range = array_values($range);
$indexes = array_keys($range);
$diff = array_diff($indexes,$range);
echo $diff[0]; // >> will print: 5
// if $diff is an empty array - you can print
// the "maximum value of the range plus one": $range[count($range)-1]+1
echo min(array_diff(range(0, max($range)+1), $range));
Simple
$array1 = array(0,1,2,3,4,5,6,7);// array with actual number series
$array2 = array(0,1,2,4,6,7); // array with your custom number series
$missing = array_diff($array1,$array2);
sort($missing);
echo $missing[0];
$range = array(0,1,2,3,4,6,7);
$max=max($range);
$expected_total=($max*($max+1))/2; // sum if no number was missing.
$actual_total=array_sum($range); // sum of the input array.
if($expected_total==$actual_total){
echo $max+1; // no difference so no missing number, then echo 1+ missing number.
}else{
echo $expected_total-$actual_total; // the difference will be the missing number.
}
you can use array_diff() like this
<?php
$range = array("0","1","2","3","4","6","7","9");
asort($range);
$len=count($range);
if($range[$len-1]==$len-1){
$r=$range[$len-1];
}
else{
$ref= range(0,$len-1);
$result = array_diff($ref,$range);
$r=implode($result);
}
echo $r;
?>
function missing( $v ) {
static $p = -1;
$d = $v - $p - 1;
$p = $v;
return $d?1:0;
}
$result = array_search( 1, array_map( "missing", $ARRAY_TO_TEST ) );

Generate a random number from a given set of numbers and chances

I have a list of numbers like
$list = array(1,5,19,23,59,51,24)
in actual code this is generated from database, so this array will hold up to 500 numbers that are different from each other.
each of these numbers in the database has a probability of occurring recorded. So i have a data from previous executions to generate random numbers from 1 to 500 and recorded the probabilities of each number generated for like 1000 times.
Now having list of numbers and probabilities for each number i want to write a function that will generate a random number from these 500 numbers based on their probabilities.
For example:
number 1 has a chance of: 0.00123 //0.123%
number 6 has a chance of: 0.0421 //4.21%
number 11 has a chance of: 0.0133 //1.33%
so variable $finallist will look something like this:
$finallist[1] = 0.00123;
$finallist[6] = 0.0421;
$finallist[11] = 0.0133;
Now if i run my function and pass in $finallist as a parameter i want to retrieve a random number between 1 and 6 but number 6 will have higher possibility of coming out than 1 and 11 will have higher possibility to come out than 1.
I have some functions written that deal with returning the random number based on its chance but it only takes 1 value as a parameter.
private function randomWithProbability($chance, $num, $range = false)
{
/* first generate a number 0 and 1 and see if that number is in the range of chance */
$rand = $this->getRandomFloatValue(0, 1);
if ($rand <= $chance)
{
/* the number should be returned */
return $num;
}
else
{
/* otherwise return a random number */
if ($range !== false)
{
/* make sure that this number is not same as the number for which we specified the chance */
$rand = mt_rand(1, $range);
while ($rand == $num)
{
$rand = mt_rand(1, $range);
}
return $rand;
}
}
}
if anyone knows a solution/algorithm to do this or if there is anything built in to PHP would be a big help. Thank you so much.
The basic algorithm you're looking for:
add all the probabilities together and determine the maximum
pick a random number between 0 and 1 and multiply it by the max
find the entry that corresponds with that value
Example code:
<?php
// create some weighted sample data (id => weight)
$samples = array(
'a' => 0.001,
'b' => 0.004,
'c' => 0.006,
'd' => 0.05,
'e' => 0.01,
'f' => 0.015,
'g' => 0.1
);
class Accumulator {
function __construct($samples) {
// accumulate all samples into a cumulative amount (a running total)
$this->acc = array();
$this->ids = array();
$this->max = 0;
foreach($samples as $k=>$v) {
$this->max += $v;
array_push($this->acc, $this->max);
array_push($this->ids, $k);
}
}
function pick() {
// selects a random number between 0 and 1, increasing the multiple here increases the granularity
// and randomness; it should probably at least match the precision of the sample data (in this case 3 decimal digits)
$random = mt_rand(0,1000)/1000 * $this->max;
for($i=0; $i < count($this->acc); $i++) {
// looks through the values until we find our random number, this is our seletion
if( $this->acc[$i] >= $random ) {
return $this->ids[$i];
}
}
throw new Exception('this is mathematically impossible?');
}
private $max; // the highest accumulated number
private $acc; // the accumulated totals for random selection
private $ids; // a list of the associated ids
}
$acc = new Accumulator($samples);
// create a results object to test our random generator
$results = array_fill_keys(array_keys($samples), 0);
// now select some data and test the results
print "picking 10000 random numbers...\n";
for($i=0; $i < 10000; $i++) {
$results[ $acc->pick() ]++;
}
// now show what we found out
foreach($results as $k=>$v) {
print "$k picked $v times\n";
}
The results:
> php.exe rand.php
picking 10000 random numbers...
a picked 52 times
b picked 198 times
c picked 378 times
d picked 2655 times
e picked 543 times
f picked 761 times
g picked 5413 times
Running the same code with this sample:
// samples with even weight
$samples = array(
'a' => 0.1,
'b' => 0.1,
'c' => 0.1,
'd' => 0.1
);
Produces these results:
> php.exe rand.php
picking 10000 random numbers...
a picked 2520 times
b picked 2585 times
c picked 2511 times
d picked 2384 times

How to build concatenated sms pdu? Getting junk chars

I'm trying to build some PHP code to send SMS through telnet to a SIM server, but I'm having trouble in sending concatenated messages.
I've read some things about using padding bits to make the coded message septets into octets, but I don't fully understand how it works.
I have a class that receives the phone number, the message (already split in 153chars maximum), the total number of SMSs and the order number of the present part of text.
It works as long as I have that '20' added before the $hexmessage. But I get a junk char in the beginning of the first part (before the 1st letter of my msg), and the same junk char replacing the first letter of the second part! (using '20' so it would show a blank space, but it shows a triangle)
I can't understand why, or what I'd had to change for it to work properly.
I hope that someone can help me understand what am I doing wrong.
Here's what I've got so far:
<?php
// Generate PDU string
public function generatePDUm($receiverNumber,$message, $sms_count, $msg_nr) {
//Append filler digit if number is national
if( strlen($receiverNumber)==9){
$nacional=1;
$receiverNumber = $receiverNumber."F";
$network = substr($receiverNumber, 0, 2); //NETWORK code, used to decide the SIM Card to be used
//Check for international flags and set the number type accordingly
}else{
$nacional=0;
if(substr($receiverNumber, 0, 1)=='+'){
$network = substr($receiverNumber, 4, 2); //NETWORK code, used to decide the SIM Card to be used
$receiverNumber = substr($receiverNumber, 1, 12); //remove international indicator
}
else if(substr($receiverNumber, 0, 2)== '00'){
$network = substr($receiverNumber, 5, 2); //NETWORK code, used to decide the SIM Card to be used
$receiverNumber = substr($receiverNumber, 2, 12); //remove international indicator
}
}
/* Flag the network to be used */
switch ($network){
case "92":
$network="TMN";
break;
case "96":
$network="TMN";
break;
case "91":
$network="VODAFONE";
break;
case "93":
$network="OPTIMUS";
break;
}
// Receiver number must be 10 characters long ('national nr' + filler digit) or less than 13 ('351'+'national nr'). (Portugal)
if( strlen($receiverNumber) < 10 || strlen($receiverNumber) > 12) {
// Error, not 10 or over 12 numbers long (Code 1)
$this->setErrorCode(1);
return false;
}
// Message must be 2 characters long at least
if( strlen($message) < 2 ) {
// Error, message too short (Code 2)
$this->setErrorCode(2);
return false;
}
// Message can't be longer than 153 characters. 3SMS.
if( strlen($message) > 153 ) {
// Error, message too long (Code 3)
$this->setErrorCode(3);
return false;
}
// Length of servicecenter number (00 = automatically fixed by phone)
$serviceCenterNumberLength = '00';
// SMS-? : 04=sms-deliver(recieve), 11=sms-submit, 01 = dont know but it works, 41 = SMS-SUBMIT + UDH bit (for extended/concatenated SMS)
// You can try to change this if your phone does not work with 01 command try to use 11 command
$smsType = '41';
// TP Message Reference: (placeholder), let the phone set the message reference number itself
$messageRef = '00';
// Number length. If national -> 9, if international -> 12
if($nacional==1){
$numberLength = '09';
}else{
$numberLength = '0C';
}
// Type of phone adress: (81=Unknown=10dec, 91=InternationalFormat, 92=National?)
if($nacional==1){
$numberType = '81';
}else{
$numberType = '91';
}
// Get the PDU version of the number
$number = $this->getNumberAsPDU( $receiverNumber );
// TP-PID (Protocol Identifier)
$protocolId = '00';
// TP-DCS (Data coding scheme)
$dataCodingScheme = '00';
// TP-Validity-Period (timestamp), AA=4days expiry, disabled for SonyEricsson support.
// $validityPeriod = 'A0';
// $validityPeriod = 'AA'; // Add this if the PDU command fails
/*user data header information (05 - User Data header info length
* 00 - Information element identifier for a concatenated short message
* 03 - Information element data length
* 00 - Reference number, auto
* 0.$sms_count - total SMS nr
* 0.$msg_nr - current SMS order */
$udhi = '050003000'.$sms_count.'0'.$msg_nr;
// echo 'UDHinfo: '.$udhi."\n";
// Data length of message (in hex format)
$dataLength = $this->strToHexLen($message);
// echo 'DATA LENGHT: '.$dataLength."\n\n";
// Convert message, string > 7bits > 8bits > hex
$hexMessage = $this->bit7tohex( $this->strto7bit( $message ) );
// Create the complete PDU string
$pdu = $serviceCenterNumberLength . $smsType . $messageRef . $numberLength .
$numberType . $number . $protocolId . $dataCodingScheme . $dataLength .
$udhi . '20' .$hexMessage;
/*
* Generate the length of var $pdu (pdu/2 minus 1) as pdu format requests
* The -1 is because we don't count the first two characters '00', needed for this command: 'cmgs=24'
*/
$cmgslen = strlen($pdu)/2-1;
// Build data array to return with required information
$data = array();
$data['pdu'] = $pdu;
$data['cmgslen'] = $cmgslen;
$data['rede'] = $network;
// Return the data array with PDU information
return $data;
}
// Generate PDU formatted cellphone number
private function getNumberAsPDU($number) {
// Length of number divided by 2 handle two characters each time
$length = strlen( $number )/2;
// Set counter to 1 for strlen
$i = 1;
$pduNumber = '';
// Loop to handle every 2 characters of the phone number. 06 12 34 56 78
while ($i <= $length) {
// Get 2 characters of the complete string depending on the number of the current loop.
// Then reverse these 2 characters and put them in var $pduNumber (06 = 60)
$pduNumber .= strrev( substr( $number,$i*2-2,2) );
// Counter + 1
$i++;
}
// Return the generated number
return $pduNumber;
}
/* Function to convert ascii character to 8 bits
* Much more efficient than holding a complete ASCII table
* Thanks to Mattijs F.
*/
private function asc2bin($input, $length=8) {
$bin_out = '';
// Loop through every character in the string
for($charCount=0; $charCount < strlen($input); $charCount++) {
$charAscii = ord($input{$charCount}); // ascii value of character
$charBinary = decbin($charAscii); // decimal to binary
$charBinary = str_pad($charBinary, $length, '0', STR_PAD_LEFT);
$bin_out .= $charBinary;
}
// Return complete generated string
return $bin_out;
}
// String to 7 bits array
private function strto7bit($message) {
$message = trim($message);
$length = strlen( $message );
$i = 1;
$bitArray = array();
// Loop through every character in the string
while ($i <= $length) {
// Convert this character to a 7 bits value and insert it into the array
$bitArray[] = $this->asc2bin( substr( $message ,$i-1,1) ,7);
$i++;
}
// Return array containing 7 bits values
return $bitArray;
}
// Convert 8 bits binary string to hex values (like F2)
private function bit8tohex($bin, $padding=false, $uppercase=true) {
$hex = '';
// Last item for counter (for-loop)
$last = strlen($bin)-1;
// Loop for every item
for($i=0; $i<=$last; $i++) {
$hex += $bin[$last-$i] * pow(2,$i);
}
// Convert from decimal to hexadecimal
$hex = dechex($hex);
// Add a 0 (zero) if there is only 1 value returned, like 'F'
if($padding && strlen($hex) < 2 ) {
$hex = '0'.$hex;
}
// If we want the output returned as UPPERCASE do this
if($uppercase) {
$hex = strtoupper($hex);
}
// Return the hexadecimal value
return $hex;
}
// Convert 7 bits binary to hex, 7 bits > 8 bits > hex
private function bit7tohex($bits) {
$i = 0;
$hexOutput = '';
$running = true;
// For every 7 bits character array item
while($running) {
if(count($bits)==$i+1) {
$running = false;
}
$value = $bits[$i];
if($value=='') {
$i++;
continue;
}
// Convert the 7 bits value to the 8 bits value
// Merge a part of the next array element and a part of the current one
// Default: new value is current value
$new = $value;
if(key_exists(($i+1), $bits)) {
// There is a next array item so make it 8 bits
$neededChar = 8 - strlen($value);
// Get the char;s from the next array item
$charFromNext = substr($bits[$i+1], -$neededChar);
// Remove used bit's from next array item
$bits[$i+1] = substr($bits[$i+1], 0, strlen($bits[$i+1])-$neededChar );
// New value is characters from next value and current value
$new = $charFromNext.$value;
}
if($new!='') {
// Always make 8 bits
$new = str_pad($new, 8, '0', STR_PAD_LEFT);
// The 8 bits to hex conversion
$hexOutput .= $this->bit8tohex($new, true);
}
$i++;
}
// Return the 7bits->8bits->hexadecimal generated value
return $hexOutput;
}
// String to length in Hex, String > StringLength > Hex
private function strToHexLen($message) {
// Length of the string (message)
$length = strlen( $message )+7; //+7 for UDH. the UDH is a total of (number of octets x bit size of octets) 6 x 8 = 48 bits long. Therefore a single bit of padding has to be prepended to the message. The UDH is therefore (bits for UDH / bits per septet) = (48 + 1)/7 = 7 septets in length.
// Hex value of this string length
$hex = dechex($length);
// Length of the hex value
$hexLength = strlen($hex);
// If the hex strng length is lower dan 2
if($hexLength < 2) {
// Add a 0 (zero) before it
$hex = '0'.$hex;
}
// Return the hex value in UPPERCASE characters
return strtoupper($hex);
}
}
?>
As you are already aware, creating concatenated SMS messages requires you to add a UDH before your text message. The UDH becomes part of your payload, thus reducing the number of characters you can send per segment.
As it has become part of your payload, it needs to confirm with your payloads data requirement - which is 7 bit. The UDH however, is 8 bit, which clearly complicates things.
Consider the UDH of the following:
050003000302
05 is the length of the UDH
00 is the IEI
03 is the IEDL (3 more octets)
00 is a reference (this number must be the same in each of your concatenated message UDH's)
03 is the maximum number of messages
02 is the current message number.
This is 6 octets in total - equating to 48 bits. This is all and well, but since the UDH is actually part of your SMS message, what you have to do is add more bits so that the actual message starts on a septet boundary. A septet boundary is every 7 bits, so in this case, we will have to add 1 more bit of data to make the UDH 49 bits, and then we can add our standard GSM-7 encoded characters.
You can read up more about this from Here
These questions are all over the place and no one seems to be able to answer them in way that makes any sense. Zero padding usually just makes things worse. I think easiest way to go around this design flaw in GMS standard is to use 8 bit encoding or 16-bit UCS2 even that it means less characters. In that way you don't need to care about difference in byte boundaries which is the reason creating concatenated SMS is so hard.

Categories