Welcome to WebmasterWorld Guest from 34.238.192.150

Forum Moderators: coopster & jatar k

Message Too Old, No Replies

mixed number format

     
11:17 am on Jul 26, 2017 (gmt 0)

Senior Member

WebmasterWorld Senior Member 10+ Year Member

joined:June 2, 2006
posts:2241
votes: 8


I'm having a PHP file where I store prices, and calculate discounts for various .

Numbers like 5,99 or 5.99 are taken as they are, so my discount calculations are right.

But, as in an example of Sweden, doing simple calculations like subtraction does not work as expected. This is because prices in thousands in Sweden are written as 1 099. Therefore, with the default PHP settings, this is not treated as 1099.

Now, since I use one file to store all of these prices, what would be the easiest way to isolate Sweden part from the rest?

I saw how to set the thousand separator:
number_format($number, 0, '.', ' ');


but I'm afraid of affecting the rest of my prices that are not in this format.

I thought about putting the code for Sweden at the bottom of the code, but I would still prefer to isolate it somehow.

The example of how this mixed code looks like is:
// Full prices
// Country 1 full prices
$aa11f = "1,050";
$aa12f = "2,460";
// Country 2 full prices
$bb21f = "29,99";
$bb22f = "89,99";
// Country 3 full prices
$cc31f = "669";
$cc32f = "1 699";
//Sale prices
// Country 1 sale prices
$aa11 = "950";
$aa12 = "1,460";
// Country 2 sale prices
$bb21 = "19,99";
$bb22 = "69,99";
// Country 3 sale prices
$cc31 = "569";
$cc32 = "1 299";
//Discount calculations
$aa11d = $aa11f - $aa11;
$aa12d = $aa12f - $aa12;
$bb11d = $bb11f - $bb11;
$bb12d = $bb12f - $bb12;
$cc11d = $cc11f - $cc11;
$cc12d = $cc12f - $cc12;


Thank you
10:21 am on Aug 5, 2017 (gmt 0)

Senior Member from US 

WebmasterWorld Senior Member keyplyr is a WebmasterWorld Top Contributor of All Time 10+ Year Member Top Contributors Of The Month

joined:Sept 26, 2001
posts:12913
votes: 893


Hi smallcompany, seems this thread has become overlooked for some reason. Now it's the weekend so even less activity. Hopefully someone knowledgeable will come along and offer help.
2:42 pm on Aug 5, 2017 (gmt 0)

Senior Member from US 

WebmasterWorld Senior Member lucy24 is a WebmasterWorld Top Contributor of All Time 5+ Year Member Top Contributors Of The Month

joined:Apr 9, 2011
posts:15934
votes: 888


Numbers like 5,99 or 5.99 are taken as they are

That's very good; it means your program can already distinguish between a decimal separator and a thousands separator (I assume it looks for exactly two digits following the whatever-it-is). Would it be cheating to just throw in an extra loop that converts
\d\D\d\d\d\b
into
\d\d\d\d
before continuing? That is: digit, non-digit, exactly three further digits >> remove non-digit, whatever it is.
12:51 am on Aug 6, 2017 (gmt 0)

Full Member

Top Contributors Of The Month

joined:Apr 11, 2015
posts: 328
votes: 24


I'm having a PHP file where I store prices, and calculate discounts for various .


Hi smallcompany, there would seem to be a more fundamental problem here... you are storing your "prices" as formatted strings, not numbers. Prices (regardless of country) are just numbers, so should be stored as numbers. PHP (and most computer languages) only handle decimal numbers in the format 1234.56 ie. a single dot for an optional decimal point, no commas, no spaces. (Although you can also use scientific notation, but that's not relevant here.)

Therefore, with the default PHP settings, this is not treated as 1099.


With any PHP settings. You cannot change how PHP handles/interprets numbers - you can only change how numbers are displayed. (Presumably, you would need to display the result in whatever format is expected in that country?)

For Sweden, or any country for that matter, monetary values should be stored (and calculated) as numbers (eg. 1099). They are only displayed to the end user as a formatted string (eg. "1 099"). As you say, "prices in thousands in Sweden are written as 1 099". When you punch that number into a calculator, do you enter a space?

Trying to do calculations using numbers stored as formatted strings is going to cause problems. It's only because PHP is a loosely typed language that it will even "try" to do this (without any warning or notice). Other, strongly typed languages like Java and C++ will just break. However, under the surface, PHP only performs numeric calculations on numbers. When you ask it to do a numeric calculation on a string, it must first "try" to convert that string to a number. The "problem" here is that PHP will only successfully convert strings that look like "numbers" (eg. "1234.56"). "1 099" (with a space) simply does not look enough like a number to convert "successfully" (it is seen as a single digit 1 and stops processing when it reaches the space).

Numbers like 5,99 or 5.99 are taken as they are, so my discount calculations are right.


Hhmm, check again. What do you expect "5,99" to be taken as? 5.99 or 599? "5,99" (with a comma) will be seen as a single digit 5 (not 5.99 or 599). The same as if you "try" to convert "5abc". (PHP converts from the left and stops as soon as it reaches a non-numeric type character.) So, "5,99" - "1" returns 4 (exactly).

I saw how to set the thousand separator:


The number_format() function simply controls how numbers are displayed, not how numbers are handled by PHP. money_format() might even be preferable in this instance and this also allows you to display monetary amounts according to a specific "locale". eg. Set the locale to "sweden" and call money_format() on the number and you get the formatted string with spaces as thousand separators, without having to explicitly specify the format string - because PHP (or rather, the OS/system that PHP runs on) "knows" how monetary amounts should be displayed in Sweden. However, this is not without its caveats and what method you choose and how it's implemented depends on your use case.

----

If you don't store these prices as numbers then (as a workaround) you would need to call an intermediary function to convert these "money strings" to numbers before doing any calculation. However, this might not be trivial. You might start by simply stripping commas and spaces, but some countries use commas as the decimal point separator (which I assume is what is being used here: "29,99"?), so straight away you've hit an additional complexity. This is prone to error. You then still have the problem of displaying the result in the correct format (as mentioned above).
8:52 pm on Aug 6, 2017 (gmt 0)

Senior Member

WebmasterWorld Senior Member 10+ Year Member

joined:June 2, 2006
posts:2241
votes: 8


whitespace - you're so right. My code still works, I guess thanking to that lose PHP behavior. But since all of the pricing and discount show based on that code, that'll have to be fixed - so the number is number, not just a string.

Thank you very much!
1:20 am on Aug 8, 2017 (gmt 0)

Full Member

Top Contributors Of The Month

joined:Apr 11, 2015
posts: 328
votes: 24


This "lose" PHP behaviour can unfortunately be quite "dangerous", resulting in some hard to find runtime errors.

Ok, so if these prices are staying as formatted number-like strings then an additional function will be required (as mentioned above) to convert these strings back to real numbers, before PHP can "reliably" perform calculations on them.

Currently, you have something like the following: (I've adjusted the variable names in your "discount calculations" section as they did not appear to match up with the "price variables" that preceded them.)


// Full prices
// Country 1 full prices
$aa11f = "1,050";
$aa12f = "2,460";
// Country 2 full prices
$bb21f = "29,99";
$bb22f = "89,99";
// Country 3 full prices
$cc31f = "669";
$cc32f = "1 699";
//Sale prices
// Country 1 sale prices
$aa11 = "950";
$aa12 = "1,460";
// Country 2 sale prices
$bb21 = "19,99";
$bb22 = "69,99";
// Country 3 sale prices
$cc31 = "569";
$cc32 = "1 299";

// Discount calculations
$aa11d = $aa11f - $aa11; // -949 (WRONG - Should be 100)
$aa12d = $aa12f - $aa12; // 1 (WRONG - Should be 1000)
$bb21d = $bb21f - $bb21; // 10 (CORRECT - However, this is a fluke!)
$bb22d = $bb22f - $bb22; // 20 (CORRECT - Again, a fluke!)
$cc31d = $cc31f - $cc31; // 100 (CORRECT)
$cc32d = $cc32f - $cc32; // 0 (WRONG - Should be 400)


The above "flukes" are because something like ("29,99" - "19,99") gets evaluated as (29-19) which gives the same result: 10. So, this "appears" to work correctly, but it's really just a coincidence. Change the digits after the decimal point (a comma in this "format") and it will likely give the wrong result.

You will need a function, something like the following:


/**
* Convert formatted price string to a number.
* Assumptions/rules:
* - Comma can be the decimal point(dp) character OR thousands separator
* - Dot can be a thousands separator when a comma is used as the dp character (or there is more than 1 dot)
* - When a dp char is used, assume there is always 2 decimal places
* - Space can be a thousands separator only (simply remove spaces unconditionally)
* @param string $priceStr A formatted price string of the form "1,050", "29,99", "1 299" or "1.234.567,99" etc.
* @return float The converted price as a float
*/
function priceStringToNumber($priceStr) {
$number = $priceStr;
// Check for comma as dp OR dot as thousands separator (and no dp)
if (preg_match('/,\d\d$/',$number) || preg_match('/\.\d\d\d$/',$number)) {
// 1. Remove all dots (since only used as thousands separator)
// 2. Replace comma with dot (decimal point) if applicable
$number = str_replace(['.',','], ['','.'], $number);
}
// 3. Remove all commas (since any remaining commas must be thousand separators or erroneous)
// 4. Remove all spaces (possible thousands separators or erroneous)
$number = str_replace([',',' '], '', $number);

return (float)$number;
}


You can then call priceStringToNumber($aa11f) to return the actual numeric value (eg. 1050). See below for test cases.

You can then write a helper function to calculate the discount:


/**
* Calculate discount
* - Acts as a wrapper to priceStringToNumber()
* @param string $fullPriceStr Formatted full price string
* @param string $salePriceStr Formatted sale price string
* @return float The numeric value of $fullPriceStr less $salePriceStr
*/
function calcDiscount($fullPriceStr,$salePriceStr) {
$fullPrice = priceStringToNumber($fullPriceStr);
$salePrice = priceStringToNumber($salePriceStr);
$discount = $fullPrice - $salePrice;
return $discount;
}


And call it like so:


// Discount calculations
$aa11d = calcDiscount($aa11f,$aa11); // 100 (CORRECT)
$aa12d = calcDiscount($aa12f,$aa12); // 1000 (CORRECT)
$bb21d = calcDiscount($bb21f,$bb21); // 10 (CORRECT)
$bb22d = calcDiscount($bb22f,$bb22); // 20 (CORRECT)
$cc31d = calcDiscount($cc31f,$cc31); // 100 (CORRECT)
$cc32d = calcDiscount($cc32f,$cc32); // 400 (CORRECT)


This just calculates the numeric result, it doesn't format the output (ie. spaces for thousands separator etc). This could be incorporated if required. I did think that maybe the format of the output could be deduced by the format of the input. This could well work in these examples, particularly if you are only calculating "discounts" (which is always a "smaller number") - however, it may not be reliable.

The priceStringToNumber() function handles quite a range of number formats. For example:


// Test conversions
echo priceStringToNumber('1,050');// 1050
echo priceStringToNumber('29,99');// 29.99
echo priceStringToNumber('669');// 669
echo priceStringToNumber('1 669');// 1699

echo priceStringToNumber('1.669');// 1699 (Dot is thousand separator)

echo priceStringToNumber('1,234,567.99');// 1234567.99
echo priceStringToNumber('1.234.567,99');// 1234567.99
echo priceStringToNumber('1 234 567,99');// 1234567.99
echo priceStringToNumber('1 234 567.99');// 1234567.99
echo priceStringToNumber('1 234,567.99');// 1234567.99
echo priceStringToNumber('1 234.567,99');// 1234567.99

echo priceStringToNumber('1 234 567');// 1234567
echo priceStringToNumber('1,234,567');// 1234567
echo priceStringToNumber('1.234.567');// 1234567