 # Improving my fuzzy number function

Hi there everyone!

I wrote this function because I need to save space in my web panel and the user doesn’t always need to know the exact value, like this: This is the function I use for the money:

``````/* Simple numbers, math hurts. */
function fuzzyNumber(\$number){

\$numLength = strlen((string)\$number);
if(\$numLength > 12){
\$n1 = substr(\$number, -\$numLength, -12);
\$n2 = '.'.substr(round(substr(\$number, -12, -10), -1), 0, -1);
\$howBig = 't';
}elseif(\$numLength > 9){
\$n1 = substr(\$number, -\$numLength, -9);
\$n2 = '.'.substr(round(substr(\$number, -9, -7), -1), 0, -1);
\$howBig = 'b';
}elseif(\$numLength > 6){
\$n1 = substr(\$number, -\$numLength, -6);
\$n2 = '.'.substr(round(substr(\$number, -6, -4), -1), 0, -1);
\$howBig = 'm';
}elseif(\$numLength > 3){
\$n1 = substr(\$number, -\$numLength, -3);
\$n2 = '.'.substr(round(substr(\$number, -3, -1), -1), 0, -1);
\$howBig = 'k';
}else{
\$n1 = \$number;
\$n2 = '';
\$howBig = '';
}

return \$n1.\$n2.\$howBig;
}
``````

And it works but although I don’t know enough about PHP to make it better(more compact/portable/elegant), I know enough about it to know that my code can always be made better.

If anyone would like to point out what I shouldn’t have done or how I could make some part of it better, I’d love to hear about it. I learned a lot writing it but I know there’s got to be some capabilities that I didn’t discern from my Googling.

Thanks for your time!

So, your instincts are probably good, because your brain started saying “any time I am writing the same code more than once, something’s gone wrong.”

Let’s also clear something up: small letters are for small things. Big Letters are for Big Things.
We want to reduce this code as much as possible, so lets think about what you know.

A number goes ‘up’ in category for every 3 digits of its length. (Hint: there’s a pattern here, so what mathematical formula can you apply?)
The number is then rendered as the rounded value of its total, to 1 decimal place.

So, here’s your challenge. Let’s ignore the rounding for now, and focus on the indicator.

``````\$values = ["","K","M","B","T"];
\$number = 12398123512
\$output = \$number.\$values[###What Goes Here?###];
``````

Thanks for the help!

I’m not sure what to do yet. Would that be where division would occur for the number? Would the choice of the value array occur there as well?

That’s the point - the ‘choice’ of the array value is the division.

Consider:

``````\$values = ["","K","M","B","T"];
``````

0 = “”
1 = “K”
2 = “M”
etc.

So, what’s the relation between the number 1 and 1000, and 2 and 1000000?

I think what you’re pointing me towards is the number of groups of 3 zeros in the numbers, correct? I am still not seeing how to do the math in my mind but I’m thinking there would be a calculation that determines the groups of 3 in the number and as it increases, you also take the next step in the \$values array.

Like maybe as you find the groups of three, if you can’t get three numbers, then the array would be , and if you could get one group of three, (1000), you would end up with \$values. That seem like I’m on the right path?

Well you’re already doing it, but without the math.

``````if(\$numLength > 12){
}elseif(\$numLength > 9){
}elseif(\$numLength > 6){
}elseif(\$numLength > 3){
else //length <3
``````

12,9,6,3,0

turn it around…
0,3,6,9,12
… and those line up to array indexes
0,1,2,3,4

So by now it should be fairly obvious. The array index is equal to 1/3 of (the string’s length minus 1), floored.

There’s also a simpler mathematical means of determining this value. There is a mathematical operation for determining the power necessary to equal a value: `log`.
Specifically, since we’re interested in the number of 3 0’s in a number, we’re actually looking for the logarithm, base 1000.

`floor(log(\$number,1000))`

This will generate 0,1,2,3,4 etc. There’s an inverse method to this, `pow()`. We can use this to do the ‘rounding’.

4,525,801 should be represented as 4.5M.
log(1000,4,525,801) = 2.2185651513531
Floor(2.2185651513531) = 2
\$values = “M”
exp(1000,2) = 1,000,000
4,525,801 / 1,000,000 = 4.525801
Round to 1 decimal place = 4.5

1 Like

I’ve always seen this solved with constructs like `while (\$value > 1000) { \$value /= 1000; }` etc, but using `log` is much nicer. Thank you! I mean, a while loop is somewhat resilient to stupid-large numbers.

Technically, to make my code resilient to MAX_INT, you’d need…

9,223,372,036,854,775,807 = ["",“K”,“M”,“B”,"T,“Qa”,“Qi”]

The largest float that PHP can handle is 1.8e308, so you’d need an array… 104 items long? I’m not going to bother doing the full array. When you’re dealing with numbers like Googols, you’re probably beyond PHP’s intended scope.

1 Like

Hello and good morning if it’s applicable! I’ve tried my hand at implementing your solution and I must say that I’ve failed miserably. Here’s where I’m currently at:

``````/* Simple numbers, math hurts. */
function fuzzyNumber(\$number){

\$value = ['','K','M','B','T','Qa','Qi'];
\$num = round(\$number / exp(1000,2), -1);
\$val = \$value[floor(log(1000,\$number))];

return \$num.\$val;
}
``````

Which resulted in:

Warning : exp() expects exactly 1 parameter, 2 given in /var/www/clients/client1/web2/web/fivemin/includities/funks.php on line 118

Warning : Division by zero in /var/www/clients/client1/web2/web/fivemin/includities/funks.php on line 118

Warning : log(): base must be greater than 0 in /var/www/clients/client1/web2/web/fivemin/includities/funks.php on line 119

I’m trying to figure out what the errors mean but if in the meantime you could tell me whether I’m close or not and where I’m going wrong, I’d be most grateful.

Thanks for your time!

Well the first error is me messing up and using the exp() function instead of the pow() function.

The second error is due to the first error;

The third error is twofold: 1: I got the parameters backwards (in Excel they’re the other way around) and 2: Your number can’t be negative.

The fourth error is that you can’t just use a 2 in the second line. You need that value.

``````function fuzzyNumber(\$number){
\$value = ['','K','M','B','T','Qa','Qi'];
\$val = floor(log(\$number,1000));
\$num = round(\$number / pow(1000,\$val), 1);
return \$num.\$value[\$val];
}
``````

If you need to handle negative numbers, as error #3 seems to indicate;

``````function fuzzyNumber(\$number){
if(\$number == 0) { return "0"; } //Special case: Avoid division by 0.
\$value = ['','K','M','B','T','Qa','Qi'];
\$abs = abs(\$number); // Get rid of the negative.
\$val = floor(log(\$abs,1000));
\$num = round(\$abs / pow(1000,\$val), 1);
\$num *= (\$number / \$abs); //if \$number was negative, this will multiply by -1.
return \$num.\$value[\$val];
}
``````
``````echo "4,525,801 ".fuzzyNumber(4525801)."<br>";
echo "0 ".fuzzyNumber(0)."<br>";
echo "-1 ".fuzzyNumber(-1)."<br>";
echo PHP_INT_MAX." ".fuzzyNumber(PHP_INT_MAX)."<br>";
``````
``````4,525,801 4.5M
0 0
-1 -1
2147483647 2.1B
``````

(My sandbox version of PHP on my laptop is operating in 32 bit mode.)

``````function fuzzyNumber(\$number){

\$numLength = strlen((string)\$number);
if(\$numLength > 12){
\$n1 = substr(\$number, -\$numLength, -12);
\$n2 = '.'.substr(round(substr(\$number, -12, -10), -1), 0, -1);
\$howBig = 't';
}elseif(\$numLength > 9){
\$n1 = substr(\$number, -\$numLength, -9);
\$n2 = '.'.substr(round(substr(\$number, -9, -7), -1), 0, -1);
\$howBig = 'b';
}elseif(\$numLength > 6){
\$n1 = substr(\$number, -\$numLength, -6);
\$n2 = '.'.substr(round(substr(\$number, -6, -4), -1), 0, -1);
\$howBig = 'm';
}elseif(\$numLength > 3){
\$n1 = substr(\$number, -\$numLength, -3);
\$n2 = '.'.substr(round(substr(\$number, -3, -1), -1), 0, -1);
\$howBig = 'k';
}else{
\$n1 = \$number;
\$n2 = '';
\$howBig = '';
}

return \$n1.\$n2.\$howBig;
}
``````

In comparison to:

``````function fuzzyNumber(\$number){
if(\$number == 0) { return "0"; } //Special case: Avoid division by 0.
\$value = ['','K','M','B','T','Qa','Qi'];
\$abs = abs(\$number); // Get rid of the negative.
\$val = floor(log(\$abs,1000));
\$num = round(\$abs / pow(1000,\$val), 1);
\$num *= (\$number / \$abs); //if \$number was negative, this will multiply by -1.
return \$num.\$value[\$val];
}
``````

Also taking into consideration how much more your version accounts for. I’m absolutely astounded at how much cleaner my version could have been.

It’s time’s like these that I told my math teacher I’d never use it.

Thank you so much for taking the time to teach me a thing or 20. I really appreciate it.

4 Likes