2011-01-12 24 views
10

Gdy liczba zmiennoprzecinkowa musi zostać obcięta do pewnej cyfry po punkcie zmiennoprzecinkowym, okazuje się, że nie jest to łatwe. Na przykład, jeśli obcięcie należy wykonać na drugą cyfrę po punkcie, liczby powinny być następujące:
45,8976 => 45,89, 0,0185 => 0,01
(druga cyfra po punkcie nie jest zaokrąglona zgodnie z trzecią cyfrą po punkcie).
Funkcje takie jak round(), number_format(), sprintf() okrągłej liczby i wydrukować
45,8976 => 45.90, 0,0185 => 0,02
Ucinanie liczb zmiennoprzecinkowych za pomocą PHP

Spotkałem dwa rozwiązania, a ja zastanawiałem się, czy są one dobre wystarczy i co jest lepsze do wykorzystania

1. function truncNumber($number, $prec = 2) 
{ 
     return bccomp($number, 0, 10) == 0 ? $number : round($number - pow(0.1, bcadd( $prec, 1)) * 5, $prec); 
} 

2. function truncNumber($number, $prec = 2) 
{ 
    return sprintf("%.".$prec."f", floor($number*pow(10, $prec))/pow(10, $prec)); 
} 
+0

cześć. skąd masz tę funkcję? czy oni pracowali poprawnie dla ciebie? którą wybrałeś i dlaczego? – arod

Odpowiedz

16

floor zrobi co prosisz.

floor(45.8976 * 100)/100; 

Nie znajdziesz bardziej bezpośredniej funkcji do tego, ponieważ jest to trochę dziwne pytanie. Zwykle zaokrąglymy matematycznie poprawność. Z ciekawości - Po co ci to potrzebne?

+2

Tak, to prawdopodobnie najlepsze rozwiązanie do obcinania dodatnich liczb zmiennoprzecinkowych. Aby pracować z ujemnymi wartościami zmiennoprzecinkowymi, należy przyjąć wartość abs(), a następnie dodać znak. Jest to formuła pochodząca z firmy, która, jeśli generuje zbyt długi numer zmiennoprzecinkowy, wszystkie cyfry po drugim powinny być po prostu ignorowane, zamiast tego zaokrąglone :). –

+2

jeśli mamy już dwie cyfry po zmiennoprzecinkowej, ale nie wiedzieliśmy o tym i spróbujmy z twoim kodem: 'echo floor (10.04 * 100)/100;' zwróci '10.03'. – jamapag

+1

To samo co jampag. poziom echa (4,60 * 100)/100; zwróci 4,59, która jest zła. – Jimmy

1

Funkcja round() ma precyzyjny parametr oraz trzeci parametr określający metodę zaokrąglania, ale masz rację, nie obcina.

To, czego szukasz, to funkcje floor() i ceil(). Minusem jest to, że nie mają parametr precyzji, więc będziesz musiał zrobić coś takiego:

$truncated = floor($value*100)/100; 
1

widzę innej metody, aby wykonać to:

function trunc($float, $prec = 2) { 
    return substr(round($float, $prec+1), 0, -1); 
} 

Ale to nie lepiej niż jakikolwiek inny ... round może być również zastąpiony przez sprintf.

6

Aby obciąć numery „najlepsze” jest użycie (wziąłem numer tutaj, że działa na moim przykładzie):

$number = 120,321314; 

$truncate_number = number_format($number , 1); // 120,3 
$truncate_number = number_format($number , 2); // 120,32 
$truncate_number = number_format($number , 3); // 120,321 

nadzieję, że ta pomoc jest łatwe niż innych odpowiedzi tutaj, ale to jest łatwe tylko dla przypadki, w których działa. Oto przypadek to nie zadziała (demo):

$number = 10.046; 

    echo number_format($number , 2); // 10.05 

Funkcja Number_format jest trudne, można rozwiązać problem w ten sposób (z php.net):

Aby zapobiec zaokrąglenie, które występuje kiedy następna cyfra po przecinku jest ostatnim znaczącym 5 (wymienione poniżej przez kilka osób):

<?php 

function fnumber_format($number, $decimals='', $sep1='', $sep2='') { 

     if (($number * pow(10 , $decimals + 1) % 10) == 5) 
      $number -= pow(10 , -($decimals+1)); 

     return number_format($number, $decimals, $sep1, $sep2); 
} 

$t=7.15; 
echo $t . " | " . number_format($t, 1, '.', ',') . " | " . fnumber_format($t, 1, '.', ',') . "\n\n"; 
//result is: 7.15 | 7.2 | 7.1 

$t=7.3215; 
echo $t . " | " . number_format($t, 3, '.', ',') . " | " . fnumber_format($t, 3, '.', ',') . "\n\n"; 
//result is: 7.3215 | 7.322 | 7.321 
} ?> 
+3

jeśli $ number będzie 10.046, twój kod zwróci 10,05, a nie 10.04. – jamapag

+0

Masz rację, chłopaki, ale tęskniłem za tym zachowaniem, ale szczerze uważam, że zachowanie jest dziwne, mam na myśli "number_format", nazwa tej funkcji sprawia, że ​​myślisz jak formowanie ekranu tylko przesuwaniem przecinka. – sandino

12

to jest moje rozwiązanie:

/** 
* @example truncate(-1.49999, 2); // returns -1.49 
* @example truncate(.49999, 3); // returns 0.499 
* @param float $val 
* @param int f 
* @return float 
*/ 
function truncate($val, $f="0") 
{ 
    if(($p = strpos($val, '.')) !== false) { 
     $val = floatval(substr($val, 0, $p + 1 + $f)); 
    } 
    return $val; 
} 
+0

dlaczego 'abs()', nie powinno być potrzebne. dobrze? – hakre

+0

To prawda. Naprawdę nie pamiętam, dlaczego musiałem dodać abs() w tej funkcji –

+0

, więc mając coś, czego nie wiemy, czy jest to konieczne czy nie, nie może być * "najprostszym" * na moich oczach (bez urazy proszę, po prostu mówiąc, że powinieneś nieco zmienić to zdanie i usunąć niezbędne części z twojej odpowiedzi, aby uzyskać lepszą informację zwrotną) – hakre

2

Chociaż to pytanie jest stare, opublikuję kolejną odpowiedź, na wypadek gdyby ktoś natknął się na ten problem, tak jak ja.Prostym rozwiązaniem dla liczb dodatnich jest:

function truncate($x, $digits) { 
    return round($x - 5 * pow(10, -($digits + 1)), $digits); 
} 

Testowałem go na rozwiązania oparte na sznurek z 10 milionów frakcji losowych i zawsze dopasowane. Nie ma problemu z precyzją, jaką mają rozwiązania oparte na floor.

Rozszerzenie na zero i negatywy jest dość proste.

1

Aby to zrobić dokładnie zarówno ve i ve numery trzeba użyć:
- funkcja php floor() dla dodatniej liczby
- funkcja php ceil() dla liczb ujemnych

function truncate_float($number, $decimals) { 
    $power = pow(10, $decimals); 
    if($number > 0){ 
     return floor($number * $power)/$power; 
    } else { 
     return ceil($number * $power)/$power; 
    } 
} 

powodem jest to, że floor() zawsze zaokrągla numer w dół, a nie w kierunku zera.
tj floor() skutecznie uzupełnia liczbie ujemnych do większej wartości bezwzględnej
np floor(1.5) = 1 podczas floor(-1.5) = 2

Dlatego w sposobie obcinania pomocą multiply by power, round, then divide by power:
- floor() działa tylko dla dodatnich liczb
- ceil() działa tylko do liczby ujemne

Aby to sprawdzić, skopiuj poniższy kod do edytora http://phpfiddle.org/lite (lub podobnego):

<div>Php Truncate Function</div> 
<br> 
<?php 
    function truncate_float($number, $places) { 
     $power = pow(10, $places); 
     if($number > 0){ 
      return floor($number * $power)/$power; 
     } else { 
      return ceil($number * $power)/$power; 
     } 
    } 

    // demo 
    $lat = 52.4884; 
    $lng = -1.88651; 
    $lat_tr = truncate_float($lat, 3); 
    $lng_tr = truncate_float($lng, 3); 
    echo 'lat = ' . $lat . '<br>'; 
    echo 'lat truncated = ' . $lat_tr . '<br>'; 
    echo 'lat = ' . $lng . '<br>'; 
    echo 'lat truncated = ' . $lng_tr . '<br><br>'; 

    // demo of floor() on negatives 
    echo 'floor (1.5) = ' . floor(1.5) . '<br>'; 
    echo 'floor (-1.5) = ' . floor(-1.5) . '<br>'; 
?> 
8
function truncate($value) 
{ 
    if ($value < 0) 
    { 
     return ceil((string)($value * 100))/100; 
    } 
    else 
    { 
     return floor((string)($value * 100))/100; 
    } 
} 

Dlaczego nie PHP obsłużyć wzorach mathamatic drogę oczekiwalibyśmy, na przykład floor(10.04 * 100) = 1003, gdy wszyscy wiemy, że powinien to być 1004. Ten problem występuje nie tylko w PHP, ale można go znaleźć we wszystkich językach, w zależności od względnego błędu użytego języka langauge. PHP używa formatu podwójnej precyzji IEEE 754, którego błąd względny wynosi około 1,11e-16. (resource)

Prawdziwym problemem jest to, że funkcja floor rzuca wartość float na wartość int, np. (int)(10.04 * 100) = 1003 jak widzimy w funkcji podłogi wcześniej. (resource)

Aby rozwiązać ten problem, możemy rzucić float na ciąg znaków, ciąg może dowolnie reprezentować dowolną wartość, a następnie funkcja floor będzie rzucała strunę dokładnie na int.

0

Chciałbym dodać mój wkład do tej odpowiedzi za pomocą funkcji używanej do moich potrzeb, która uwzględnia skrócone pozycje zarówno dla wartości ujemnych, jak i dodatnich.

function truncate($val,$p = 0) 
{ 
    $strpos = strpos($val, '.'); 
    return $p < 0 ? round($val,$p) : ($strpos ? (float)substr($val,0,$strpos+1+$p) : $val); 
} 

... Myślę, że jest prosty i szybki w użyciu.

0

Powodem tego jest wewnętrzna reprezentacja binarna liczb zmiennoprzecinkowych. Podana wartość z powyższego 10.04 musi być reprezentowana wewnętrznie jako potęga podstawy 2.

Potencjalny wykładnik dla części zmiennoprzecinkowej to ln(0,04)/ln(2) = -4,64.... To pokazuje, że 10.04 nie może być dokładnie odwzorowany jako liczba binarna. W końcu mamy coś w rodzaju 10.04 ~ 2^3 + 2^1 + 2^-5 + 2^-7 + 2^-11 + ... z maksymalną precyzją dla części zmiennoprzecinkowej.

Jest to powód, dla którego 10.04 jest wewnętrznie nieco mniejszy i może być reprezentowany jako 10.039....

Aby obejść to zachowanie, istnieją dwie możliwości - albo skrzypce sąsiadują bezpośrednio z operacjami ciągowymi, albo korzystają z arbitralnej biblioteki precyzyjnej, takiej jak BcMath dla PHP.

<?php 
function test($value) 
{ 
    // direct string operations 
    $fromString = (float)preg_replace(
     '#(?:(\.\d{1,2})\d*)?$#', 
     '${1}', 
     (string)$value 
    ); 
    // using arbitrary precision 
    // @see http://php.net/manual/en/function.bcadd.php 
    $fromBcMath = (float)bcadd(
     (string)$value, 
     '0', 
     2 
    ); 

    var_dump($fromString, $fromBcMath); 
} 

test(3); 
test(4.60); 
test(10.04); 
test(45.8976); 

Wyjście kodu przedstawionym powyżej (I dodaje nowe linie oddzielające do odczytu)

double(3) 
double(3) 

double(4.6) 
double(4.6) 

double(10.04) 
double(10.04) 

double(45.89) 
double(45.89) 
Powiązane problemy