PiTheNumber
PiTheNumber

Reputation: 23552

Price string to float

I like to convert string with a price to a float value. The price comes from different languages and countries and can look like this:

 1,00 €
 € 1.00
 1'000,00 EUR
 1 000.00$
 1,000.00$
 1.000,00 EURO

or whatever you can think of...

Not sure I got the full range of possibilities with my examples. I am also not sure if it is possible to make in international convert blindly, maybe I have to use a language code? So for the start Euro and Dollar would be enough.

floatval() is kind of stupid so I need something more here. I think I should first remove all chars beside numbers, , and .. Then fix the , / . and use floatval finally.

Has someone done this before and can help me a little?

I would prefer a solution without regexp ;)

Update: Current solution with tests https://onlinephp.io/c/9e95e

Upvotes: 9

Views: 21901

Answers (7)

D B
D B

Reputation: 1

I got most of the way with the help of this particular thread, however I was still able to generate edge cases.

The main issue is the position of the decimal point and comma across currencies, so performing incremental checks is needed.

  //remove everything except for numbers, decimals, commas and hyphens 
    $value = preg_replace('/[^0-9.,-]+/', '', $value);
    
    $decimal = strpos($value, '.');
    $comma = strpos($value, ',');
    
    //check the 3rd last character
    if(!in_array(substr($value, -3, 1), [".", ","])) {

        if($comma && (substr($value, -3, 1) != ".")) {
            $value .= ".00";
        } elseif($decimal && (substr($value, -3, 1) != ",")) {
            $value .= ",00";
        }

    }

    $decimal = strpos($value, '.');
    $comma = strpos($value, ',');

    if($comma === false) //no comma must be a decimal number already
        return (float) $value;

    if($decimal < $comma){ //decimal before a comma = euro
        $value = str_replace(['.',','], ['','.'], $value);
        return (float) $value;
    }

    //comma first = traditional thousan separator
    $value = str_replace(',', '', $value);
    
    return (float)$value;

Upvotes: 0

PiTheNumber
PiTheNumber

Reputation: 23552

Ok, I tried it myself. What do you think of this?

function priceToFloat($s){
    // is negative number
    $neg = strpos((string)$s, '-') !== false;
    
    // convert "," to "."
    $s = str_replace(',', '.', $s);

    // remove everything except numbers and dot "."
    $s = preg_replace("/[^0-9\.]/", "", $s);

    // remove all seperators from first part and keep the end
    $s = str_replace('.', '',substr($s, 0, -3)) . substr($s, -3);

    // Set negative number
    if( $neg ) {
        $s = '-' . $s;
    }

    // return float
    return (float) $s;
}

Here some tests: https://onlinephp.io/c/9e95e

Sorry. I couldn't include the other functions because codepad did not like them. But I compared them and there was trouble with strings like "22 000,76" or "22.000"

Update: As Limitless isa pointed out you might have a look at the build in function money-format.

Upvotes: 21

York
York

Reputation: 31

not perfect, but it work

function priceToFloat($s){
    // clear witespaces
    $s = trim($s);
    $s = str_replace(' ', '', $s);

    // is it minus value
    $is_minus = false;
    if(strpos($s, '(') !== false)
        $is_minus = true;
    if(strpos($s, '-') !== false)
        $is_minus = true;

    // check case where string has "," and "."
    $dot = strpos($s, '.');
    $semi = strpos($s, ',');
    if($dot !== false && $semi !== false){
        // change fraction sign to #, we change it again later
        $s = str_replace('#', '', $s); 
        if($dot < $semi) $s = str_replace(',','#', $s);
        else $s = str_replace('.','#', $s);

        // remove another ",", "." and change "#" to "."
        $s = str_replace([',','.', '#'], ['','', '.'], $s);
    } 
    $s = str_replace(',', '.', $s); 
    // clear usless elements
    $s = preg_replace("/[^0-9\.]/", "", $s);

    // if it minus value put the "-" sign
    if($is_minus) $s = -$s;
    return (float) $s;
}

working cases

$prices = [
    '123.456,789',
    '123,456.789',
    '123 456,789',
    '123 456.789',
    '-123,456.789',
    '(123,456.789)',
];
foreach($prices as $price)
     echo priceToFloat($price).'<br />';

return

123456.789
123456.789
123456.789
123456.789
-123456.789
-123456.789

Upvotes: 1

Limitless isa
Limitless isa

Reputation: 3802

price to number number to price examples

<?php

    $number="1.050,50";

    $result=str_replace(',','.',str_replace('.','',$number));
    echo $result. "<br/>";
    // 1050.50

    setlocale(LC_MONETARY, 'tr_TR');
    echo money_format('%!.2n', $result) ;
    // 1.050,50

?>

Upvotes: 1

user1988125
user1988125

Reputation:

This function will fix your problem:

function priceToSQL($price)
{
    $price = preg_replace('/[^0-9\.,]*/i', '', $price);
    $price = str_replace(',', '.', $price);

    if(substr($price, -3, 1) == '.')
    {
        $price = explode('.', $price);
        $last = array_pop($price);
        $price = join($price, '').'.'.$last;
    }
    else
    {
        $price = str_replace('.', '', $price);
    }

    return $price;
}

Upvotes: 2

Nick
Nick

Reputation: 6346

To remove all but numbers, commas and full stops:

<?php

$prices = array( "1,00 €",
 "€ 1.00",
 "1'000,00 EUR",
 "1 000.99$",
 "1,000.01$",
 "1.000,10 EURO");

$new_prices = array();
foreach ($prices as $price) {
    $new_prices[] = preg_replace("/[^0-9,\.]/", "", $price);
}

print_r($new_prices);

Output:

Array ( [0] => 1,00 [1] => 1.00 [2] => 1000,00 [3] => 1000.99 [4] => 1,000.01 [5] => 1.000,10 )

Now lets utilize the parseFloat function from Michiel - php.net (I won't paste it here since it's a pretty big function):

<?php

$prices = array( "1,00 €",
 "€ 1.00",
 "1'000,00 EUR",
 "1 000.99$",
 "1,000.01$",
 "1.000,10 EURO");

$new_prices = array();
foreach ($prices as $price) {
    $new_prices[] = parseFloat(preg_replace("/[^0-9,\.]/", "", $price));
}

print_r($new_prices);

Output will be:

Array ( [0] => 1 [1] => 1 [2] => 1000 [3] => 1000.99 [4] => 1000.01 [5] => 1000.1 )

Upvotes: 1

Oldskool
Oldskool

Reputation: 34867

Removing all the non-numeric characters should give you the price in cents. You can then divide that by 100 to get the 'human readable' price. You could do this with something like the filter_var FILTER_SANITIZE_NUMBER_INT. For example:

$cents = filter_var($input, FILTER_SANITIZE_NUMBER_INT);
$price = floatval($cents / 100);

Above is untested, but something like that is probably what you're looking for.

Upvotes: 2

Related Questions