Advertisement
If you have a new account but are having problems posting or verifying your account, please email us on hello@boards.ie for help. Thanks :)
Hello all! Please ensure that you are posting a new thread or question in the appropriate forum. The Feedback forum is overwhelmed with questions that are having to be moved elsewhere. If you need help to verify your account contact hello@boards.ie

PHP sprintf() returning wrong value for %02

Options
  • 04-10-2019 7:05pm
    #1
    Registered Users Posts: 6,501 ✭✭✭


    I wrote code for WooCommerce to superscript the decimal number.
    It was simple enough, split out the price into unit and decimal, wrap 'sup' tags around decimal and concatenate both numbers again.

    Someone said that it didn't work for some numbers e.g. 28.95 rendered as 28.94!

    I tracked it down to a bizarre issue with sprintf(). I am using PHP 7.1.11 via XAMPP on Windows 7.

    [PHP]<?php

    foreach ( array( 28.91, 28.92, 28.93, 28.94, 28.95, 28.96, 28.97 ) as $price ) {
    $unit = intval( $price );
    $decimal = ( $price - intval( $price ) ) * 100;
    $decimal = sprintf( '<sup>%02d</sup>', $decimal );

    echo $price, ' - ', $unit . $decimal, "\n";
    }[/PHP]

    The result:
    28.91 - 28<sup>91</sup>
    28.92 - 28<sup>92</sup>
    28.93 - 28<sup>92</sup>
    28.94 - 28<sup>94</sup>
    28.95 - 28<sup>94</sup>
    28.96 - 28<sup>96</sup>
    28.97 - 28<sup>96</sup>
    

    What's going on???


Comments

  • Registered Users Posts: 6,150 ✭✭✭Talisman


    You're dealing with floating point and sprintf is not a rounding function.
    echo number_format(28.96, 16);
    28.9600000000000009
    
    echo number_format(28.97, 16);
    28.9699999999999989
    

    If you take away 28 and multiply by 100 the first 2 didgits are 96 in both cases.

    You have two options:

    1 - Use round
    $decimal = round(( $price - intval( $price ) ) * 100);
    

    2 - Stick with strings
    foreach ( array( 28.91, 28.92, 28.93, 28.94, 28.95, 28.96, 28.97 ) as $price ) {
        $parts = explode( '.', strval( $price ));
        echo $price, ' - ', $parts[0] . '<sup>' . $parts[1] . '</sup>' , "\n";
    }
    


  • Registered Users Posts: 6,501 ✭✭✭daymobrew


    Thanks.
    I'll go with round() as I want to keep the code generic without hard coding the decimal separator (which could change) - the original function receives 5 parameters:
    - formatted price (a string with localised thousands and decimal separators)
    - price (floating point number)
    - number of decimal places (generally 2 but could be more or less)
    - decimal separator
    - thousands separator


  • Registered Users Posts: 6,150 ✭✭✭Talisman


    You can always query the locale to find out the separators.
    setlocale(LC_ALL, 'en_IE');
    $locale_info = localeconv();
    print_r($locale_info);
    

    Output:
    Array
    (
        [decimal_point] => .
        [thousands_sep] => ,
        [int_curr_symbol] => EUR
        [currency_symbol] => &#8364;
        [mon_decimal_point] => .
        [mon_thousands_sep] => ,
        [positive_sign] =>
        [negative_sign] => -
        [int_frac_digits] => 2
        [frac_digits] => 2
        [p_cs_precedes] => 1
        [p_sep_by_space] => 0
        [n_cs_precedes] => 1
        [n_sep_by_space] => 0
        [p_sign_posn] => 1
        [n_sign_posn] => 1
        [grouping] => Array
            (
                [0] => 3
                [1] => 3
            )
    
        [mon_grouping] => Array
            (
                [0] => 3
                [1] => 3
            )
    
    )
    

    You can pull the individual elements that you need out of the associative array.
    echo $locale_info['decimal_point'];
    .
    
    echo $locale_info['thousands_sep'];
    ,
    


  • Registered Users Posts: 6,501 ✭✭✭daymobrew


    The code is for a WooCommerce site and the site admin sets the separators (which will probably match the locale settings).


Advertisement