Bir kayan noktalı değeri C'deki ondalık noktadan sonra yalnızca iki yerle nasıl kısıtlayabilirim?


215

Bir kayan değeri (37.777779 gibi) C'deki iki ondalık basamağa (37.78) nasıl yuvarlayabilirim?


15
Sayının kendisini düzgün şekilde yuvarlayamazsınız, çünkü float(ve double) ondalık kayan nokta değildir - ikili kayan noktadır - bu nedenle ondalık konumlara yuvarlama anlamsızdır. Ancak çıktıyı yuvarlayabilirsiniz.
Pavel Minaev

63
Anlamsız değil; bu kesin değil. Bir fark var.
Brooks Moses

2
Ne tür bir yuvarlama bekliyorsunuz? Yarım yukarı mı yoksa en yakınına yuvarlama mı?
Truthseeker Rangwan

Yanıtlar:


407

Çıktı amacıyla sayıyı yuvarlamak istiyorsanız, "%.2f"biçim dizesi gerçekten doğru cevaptır. Ancak, daha fazla hesaplama için kayan nokta değerini yuvarlamak istiyorsanız, aşağıdakine benzer:

#include <math.h>

float val = 37.777779;

float rounded_down = floorf(val * 100) / 100;   /* Result: 37.77 */
float nearest = roundf(val * 100) / 100;  /* Result: 37.78 */
float rounded_up = ceilf(val * 100) / 100;      /* Result: 37.78 */

Seçmek isteyebileceğiniz üç farklı yuvarlama kuralı olduğuna dikkat edin: aşağı yuvarlayın (yani, iki ondalık basamaktan sonra kısaltın), en yakına yuvarlayın ve yuvarlayın. Genellikle, en yakına yuvarlamak istersiniz.

Bazılarının işaret ettiği gibi, kayan nokta gösterimi tuhaflıkları nedeniyle, bu yuvarlanmış değerler tam olarak "açık" ondalık değerler olmayabilir, ancak çok çok yakın olacaktır.

Yuvarlama hakkında çok daha fazla bilgi için ve özellikle en yakınına yuvarlama için bağlayıcı kurallar hakkında daha fazla bilgi için Yuvarlama hakkındaki Wikipedia makalesine bakın .


4
Rastgele hassasiyete yuvarlamayı destekleyecek şekilde değiştirilebilir mi?

1
@slater 'Rasgele kesinlik' dediğinizde, örneğin, iki ondalık basamak yerine üçe yuvarlamayı mı, yoksa sınırsız kesinlik ondalık değerlerini uygulayan kitaplıkları mı kullanmayı istiyorsunuz? Birincisi, 100 sabitini ayarlamak için umarım ne yapın; aksi takdirde, kullandığınız çok hassas kitaplık ile aynı hesaplamaları yapın.
Dale Hagglund

2
@DaleHagglung Eski, teşekkürler. 100 yerine pow (10, (int) istenilen Hassasiyet) koyulur mu?

3
Evet. K ondalık basamaktan sonra yuvarlamak için 10 ^ k ölçek faktörü kullanın. Bazı ondalık değerleri elle yazıp 10'un katlarıyla oynayıp oynamadığınızı görmek gerçekten kolay olmalı. 1.23456789 değeriyle çalıştığınızı ve 3 ondalık basamağa yuvarlamak istediğinizi varsayalım. Kullanabileceğiniz işlem tam sayıya yuvarlanır . Peki, ondalık noktadan ayrılmak için ilk üç ondalık basamağı nasıl hareket ettirirsiniz? Umarım 10 ^ 3 ile çarptığınız açıktır. Şimdi bu değeri bir tamsayıya yuvarlayabilirsiniz. Ardından, 10 ^ 3'e bölerek düşük üç haneyi geri koyarsınız.
Dale Hagglund

1
Bu işi bir doublesşekilde yapabilir miyim ? İstediğim işi yapmıyormuş gibi görünüyor :( ( floorve kullanarak ceil).
Bayan Kimse

87

Printf içinde % .2f kullanma . Yalnızca 2 ondalık nokta yazdırır.

Misal:

printf("%.2f", 37.777779);

Çıktı:

37.77

Bu şekilde daha iyidir çünkü hassasiyet kaybı yoktur.
albert

2
@albert Bu aynı zamanda taşma floatgibi aralık kaybı olmaması avantajına da sahiptir val * 100.
chux - Monica'yı geri yükle

42

Eğer baskı için değer yuvarlak bahsediyoruz varsayarsak, o zaman Andrew Coleson ve Arak 'ın cevabı doğrudur:

printf("%.2f", 37.777779);

Ancak, dahili kullanım için sayıyı tam olarak 37,78'e yuvarlamayı hedefliyorsanız (örneğin, başka bir değerle karşılaştırmak için), kayan nokta sayılarının çalışma şekli nedeniyle bunun iyi bir fikir olmadığını unutmayın: genellikle kayan nokta için eşitlik karşılaştırmaları yapmak yerine, bir hedef değeri +/- bir sigma değeri kullanın. Ya da sayıyı bilinen bir kesinlik ile dize olarak kodlayın ve karşılaştırın.

Greg Hewgill'in finansal hesaplamalar için neden kayan nokta kullanmamanız gerektiğini de kapsayan ilgili bir soruya verdiği cevaba bakın .


1
Sorunun arkasındaki sorunun ne olabileceğini (veya sorunun arkasında olması gereken soruyu!) Ele almak için oy verildi. Bu oldukça önemli bir nokta.
Brooks Moses

Aslında 37.78 tam olarak kayan nokta ile gösterilebilir. Şamandıra, hassasiyet için 11 ila 12 basamaklıdır. Bu 3778 377.8 veya her türlü 4 ondalık basamağı ele almak için yeterli olmalıdır.
Anonim Beyaz

@HaryantoCiu evet yeterince adil, cevabımı biraz değiştirdim.
John Carter

dinamik hassasiyet:printf("%.*f", (int)precision, (double)number);
Minhas Kamal

24

Buna ne dersin:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);

4
-1: a) bu negatif sayılar için işe yaramaz (tamam, örnek pozitif ama yine de). b) tam ondalık değeri kayan
John Carter

32
@therefromhere: (a) Haklısın (b) Bu nedir? Lise sınavı mı?
Daniil

1
neden 0.5 eklediniz?
muhammed tayyab

1
Yuvarlama kurallarına uymak gerekir.
Daniil

1
kuralları yuvarlama @Daniil yorumun bağlamında olan en yakın yuvarlak
Shmil Kedi

20
printf("%.2f", 37.777779);

C-string'e yazmak istiyorsanız:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);

@Sinan: Neden düzenleme? @AraK: Hayır, sen büyüklük :) dikkatli olmalıdır. Snprintf () kullanın.
aib

1
@aib: Tahmin ediyorum çünkü / ** / C tarzı yorumlar ve soru C için etiketlendi
Michael Haren

5
C89 sadece / ** / - stiline izin verdi, C99 // - style desteği verdi. Topal / eski bir derleyici kullanın (veya C89 modunu zorlayın) / / style kullanamazsınız. Bunu söyledikten sonra, 2009, onları hem C hem de C ++ stili olarak düşünelim.
Andrew Coleson

11

A'yı floatdiğerine yuvarlamanın bir yolu yoktur floatçünkü yuvarlatılmış floattemsil edilemeyebilir (kayan noktalı sayıların bir sınırlaması). Örneğin, 37.777779'u 37.78'e yuvarladığınızı, ancak temsil edilebilecek en yakın sayı 37.781 olduğunu varsayalım.

Ancak, can "yuvarlak" bir floatbir biçim dizesi işlevini kullanarak.


3
Bu, "iki şamandırayı bölmenin ve bir şamandıra elde etmenin bir yolu yok" demekten farklı değildir, çünkü bölünmüş sonuç temsil edilemeyebilir, ki bu tam olarak doğru olabilir ancak önemsizdir. Şamandıralar, toplama kadar temel bir şey için bile her zaman tam değildir; varsayım her zaman gerçekte elde ettiğiniz şeyin "tam yuvarlak cevaba en yakın yaklaşan şamandıra" olduğudur.
Brooks Moses

Demek istediğim, a floatile n arasındaki ondalık basamakları yuvarlayamazsınız ve sonuçta her zaman n ondalık basamak olmasını beklemezsiniz. Hala floatbeklediğin değil, bir alacaksın.
Andrew Keeton

9

Ayrıca, C ++ kullanıyorsanız, sadece böyle bir işlev oluşturabilirsiniz:

string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

Ardından herhangi bir çift çıkış myDoubleile nbu gibi kodla ondalık noktadan sonraki basamağa:

std::cout << prd(myDouble,n);

7

Yine de şunları kullanabilirsiniz:

float ceilf(float x); // don't forget #include <math.h> and link with -lm.

misal:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;

Bu ondalık noktada kesilir (yani 37 üretecektir) ve ondalık noktadan sonra iki yere yuvarlanması gerekir .
Pavel Minaev

Ondalık noktadan sonra iki yere yuvarlama önemsiz bir varyasyon olsa da (yine de cevapta belirtilmelidir; ZeroCool, düzenleme eklemek ister misiniz?): Float roundedValue = ceilf (valueToRound * 100.0) / 100.0;
Brooks Moses

Uyku hali içine :)
ZeroCool

Neden bu çözüm daha popüler değil? Bu, minimum kodla tam olarak nasıl olması gerektiğini gösterir. Bununla ilgili bir uyarı var mı?
Andy

7

C ++ 'da (veya C stili dökümlerle C'de), işlevi oluşturabilirsiniz:

/* Function to control # of decimal places to be output for x */
double showDecimals(const double& x, const int& numDecimals) {
    int y=x;
    double z=x-y;
    double m=pow(10,numDecimals);
    double q=z*m;
    double r=round(q);

    return static_cast<double>(y)+(1.0/m)*r;
}

Sonra std::cout << showDecimals(37.777779,2);üretecek: 37.78.

Açıkçası bu işlevdeki 5 değişkenin tamamını oluşturmanıza gerek yok, ama mantığı görebilmek için onları orada bırakıyorum. Muhtemelen daha basit çözümler var, ama bu benim için iyi çalışıyor - özellikle ondalık basamaktan sonraki basamak sayısını ihtiyacım olarak ayarlamama izin verdiği için.


5

Bunun için her zaman printffonksiyon ailesini kullanın . Değeri bir kayan nokta olarak almak isteseniz bile, en iyi snprintfyuvarlatılmış değeri bir dize olarak almak ve daha sonra aşağıdakileri kullanarak ayrıştırmak için kullanamazsınız atof:

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

Bunu söylüyorum, çünkü şu anda en çok oy alan cevap ve burada bulunan diğer birkaç kişi tarafından gösterilen yaklaşım - 100 ile çarpılıyor, en yakın tam sayıya yuvarlanıyor ve sonra tekrar 100'e bölünüyor - iki şekilde kusurlu:

  • Bazı değerler için yanlış yönde yuvarlanır çünkü 100 ile çarpma, kayan nokta sayılarının belirsizliği nedeniyle yuvarlama yönünü 4'ten 5'e belirleyen ondalık basamağı değiştirir veya tersi de geçerlidir.
  • Bazı değerler için çarpma ve sonra 100 ile bölme gidiş-dönüş yapmaz, yani yuvarlama olmasa bile sonuç yanlış olur

İlk hata türünü (yuvarlama yönü bazen yanlış) göstermek için bu programı çalıştırmayı deneyin:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50f\n", res1);
    printf("Rounded with round, then divided: %.50f\n", res2);
}

Bu çıktıyı göreceksiniz:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Başladığımız değerin 0,015'ten az olduğunu ve bu nedenle 2 ondalık basamağa yuvarlarken matematiksel olarak doğru cevabın 0,01 olduğunu unutmayın. Tabii ki, 0.01 tam olarak bir çift olarak temsil edilemez , ancak sonucumuzun 0.01'e en yakın çift olmasını bekliyoruz. Kullanmak snprintfbize bu sonucu verir, ancak kullanmak round(100 * x) / 100bize 0.02 verir, bu yanlıştır. Neden? Çünkü 100 * xsonuç olarak bize tam olarak 1.5 veriyor. 100 ile çarpıldığında, yuvarlamak için doğru yön değişir.

Örnek olarak ikinci arızanın cinsini - Sonuç bazen nedeniyle yanlış olma * 100ve / 100gerçekten birbirlerine tersidir olmamak - biz çok büyük bir sayı ile benzer bir egzersiz yapabilirsiniz:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1f\n", res1);
    printf("Rounded with round, then divided: %.1f\n", res2);
}

Artık sayımızın kesirli bir parçası bile yok; yalnızca tür ile depolanan bir tamsayı değeridir double. Yuvarlamadan sonraki sonuç, başladığımız sayı ile aynı olmalı, değil mi?

Yukarıdaki programı çalıştırırsanız şunları görürsünüz:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Hata. Yöntemimiz snprintftekrar doğru sonucu döndürür, ancak çarpma-sonra-yuvarlak-sonra-bölme yaklaşımı başarısız olur. Matematiksel doğru değeri olmasıdır 8631192423766613.0 * 100, 863119242376661300.0değil, bir çift tam olarak sunulabilen; en yakın değer 863119242376661248.0. Bunu 100'e böldüğünüzde, 8631192423766612.0başladığınızdan farklı bir sayı elde edersiniz.

Umarım bu roundf, birkaç ondalık basamağa yuvarlamak için kullanmanın bozulduğunu ve bunun snprintfyerine kullanmanız gerektiğini gösteren yeterli bir gösteri . Bu size korkunç bir saldırı gibi geliyorsa, belki de CPython'un yaptığı şeyden emin olabilirsiniz .


IEEE kayan noktasının tuhaflığı ve doğrudan bir alternatif sağlaması sayesinde cevabımla ve neyinle neyin yanlış gittiğine dair somut bir örnek için +1. Çevresel olarak çok uzun zaman önce, baskıya harcanan çok çaba harcadığımı ve arkadaşların bana yuvarlak-kayan kayan nokta değerleri için güvenli olduklarını biliyordum. O zaman yapılan işin burada ortaya çıktığını tahmin ediyorum.
Dale Hagglund

Ahem ... Sonunda yakın salata kelimesi için özür dilerim. Söylemek istediğim "... printf ve arkadaşlarına onları güvende tutmak için çok çaba sarf etmekti" ...
Dale Hagglund

4

Kullanın float roundf(float x).

"Yuvarlama işlevleri, argümanlarını kayan nokta biçiminde en yakın tamsayı değerine yuvarlar ve mevcut yuvarlama yönüne bakılmaksızın, yarım vakaları sıfırdan uzağa yuvarlar." C11dr §7.12.9.5

#include <math.h>
float y = roundf(x * 100.0f) / 100.0f; 

Uygulamanıza bağlı olarak, floatyarı yolda görünebilecek sayılar değildir. kayan nokta tipik olarak baz-2 yönelimlidir. Ayrıca, 0.01tüm "yarı yol" davalarında en yakın olana tam olarak yuvarlamak en zordur.

void r100(const char *s) {
  float x, y;
  sscanf(s, "%f", &x);
  y = round(x*100.0)/100.0;
  printf("%6s %.12e %.12e\n", s, x, y);
}

int main(void) {
  r100("1.115");
  r100("1.125");
  r100("1.135");
  return 0;
}

 1.115 1.115000009537e+00 1.120000004768e+00  
 1.125 1.125000000000e+00 1.129999995232e+00
 1.135 1.134999990463e+00 1.139999985695e+00

"1.115" Her ne kadar dönüştürülür 1.11 ve 1.12 arasında, "yarım" bir floatdeğer olup, 1.115000009537...ve "yarım" artık, ama daha yakından 1.12 ve mermi için en yakın olan floatve1.120000004768...

"1.125", 1.12 ve 1.13 arasında "yarı yol" dur, dönüştürüldüğünde floatdeğer tam olarak 1.125ve "yarı yol" dur. Dolayı en yakın bile kurala bağları ve mermi ile 1.13 doğru yuvarlar floatarasında1.129999995232...

"1.135" Her ne kadar dönüştürülür 1.13 ve 1.14 arasında, "yarım" bir floatdeğer olup, 1.134999990463...ve "yarım" artık, ama daha yakından 1.13 ve mermi için en yakın olan floatve1.129999995232...

Kod kullanılırsa

y = roundf(x*100.0f)/100.0f;

"1.135" dönüştürülür 1.13 ve 1.14 arasında "yarım", her ne kadar float, bir değerdir 1.134999990463...ve "yarım" artık, ama daha yakından 1.13 ama yanlış için mermi floatarasında 1.139999985695...daha sınırlı hassas nedeniyle floatvs double. Bu yanlış değer, kodlama hedeflerine bağlı olarak doğru olarak görülebilir.


4

Bu makroyu float sayılarını yuvarlamak için yaptım. Başlığınıza / dosya varlığınıza ekleyin

#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

İşte bir örnek:

float x = ROUNDF(3.141592, 100)

x eşittir 3.14 :)


Bu kesilir, ancak soru yuvarlama ister. Ayrıca, kayan nokta işlemlerinde yuvarlama hatalarına tabidir.
Eric Postpischil

3
double f_round(double dval, int n)
{
    char l_fmtp[32], l_buf[64];
    char *p_str;
    sprintf (l_fmtp, "%%.%df", n);
    if (dval>=0)
            sprintf (l_buf, l_fmtp, dval);
    else
            sprintf (l_buf, l_fmtp, dval);
    return ((double)strtod(l_buf, &p_str));

}

İşte nondalık sayıların sayısı

misal:

double d = 100.23456;

printf("%f", f_round(d, 4));// result: 100.2346

printf("%f", f_round(d, 2));// result: 100.23

Dört nedenden dolayı -1: 1) açıklama eksikliği, 2) taşma arabelleğine açıklık - bu taşacak ve bu nedenle büyükse dvalbüyük olasılıkla çökecektir 3) her dalda aynı şeyi yaptığınız garip if/ elseblok ve 4) sprintfikinci bir sprintfçağrı için format belirleyiciyi oluşturmak için aşırı karmaşık kullanımı ; .*aynı sprintfçağrının argüman olarak çift değerini ve ondalık basamak sayısını kullanmanız ve iletmeniz daha kolaydır .
Mark Amery

3

Kod tanımı:

#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))

Sonuçlar :

a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430

0

Öncelikle bu soruya başka bir cevap daha eklememe nedenimi gerekçelendirmeye çalışayım. İdeal bir dünyada, yuvarlama gerçekten önemli değildir. Bununla birlikte, gerçek sistemlerde, beklediğiniz gibi olmayan yuvarlama ile sonuçlanabilecek çeşitli sorunlarla uğraşmanız gerekebilir. Örneğin, nihai sonuçların yuvarlandığı ve kullanıcılara 2 ondalık basamak olarak gösterildiği finansal hesaplamalar yapıyor olabilirsiniz; bu aynı değerler 2'den fazla ondalık basamak içerebilecek bir veritabanında sabit bir hassasiyetle depolanır (çeşitli nedenlerle saklanacak en uygun yer sayısı yoktur ... her sistemin desteklemesi gereken belirli durumlara bağlıdır, örneğin fiyatları birim başına bir kuruşun kısımlarıdır); ve sonuçların artı / eksi epsilon olduğu değerlerde gerçekleştirilen kayan nokta hesaplamaları. Yıllardır bu sorunlarla yüzleşiyorum ve kendi stratejimi geliştiriyorum. Her senaryo ile karşılaştığımı veya en iyi cevaba sahip olduğumu iddia etmeyeceğim, ancak aşağıda bu konuların üstesinden gelen yaklaşımımın bir örneği:

Aşağıdaki yuvarlama işlevini / yöntemini kullanarak şamandıralar / çiftler (belirli bir uygulama için keyfi bir karar) üzerinde hesaplamalar için 6 ondalık basamağın yeterli hassasiyet olarak kabul edildiğini varsayalım:

double Round(double x, int p)
{
    if (x != 0.0) {
        return ((floor((fabs(x)*pow(double(10.0),p))+0.5))/pow(double(10.0),p))*(x/fabs(x));
    } else {
        return 0.0;
    }
}

Bir sonucun sunumu için 2 ondalık basamağa yuvarlama şu şekilde yapılabilir:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));

Çünkü val = 6.825sonuç 6.83beklendiği gibi.

Çünkü val = 6.824999sonuç 6.82. Burada varsayım, hesaplamanın tam olarak sonuçlandığı 6.824999ve 7. ondalık basamağın sıfır olduğu varsayımıdır .

Çünkü val = 6.8249999sonuç 6.83. 9Bu durumda 7. ondalık basamak , Round(val,6)işlevin beklenen sonucu vermesine neden olur . Bu durumda, herhangi bir sayıda takip edilebilir 9.

Çünkü val = 6.824999499999sonuç 6.83. Birinci basamak olarak 8. ondalık basamağa yuvarlama, yani Round(val,8), hesaplanan bir kayan nokta sonucunun hesaplandığı 6.8249995, ancak dahili olarak temsil edildiği tek kötü durumla ilgilenir 6.824999499999....

Son olarak, sorudan örnek ... ile val = 37.777779sonuçlanır 37.78.

Bu yaklaşım şu şekilde genelleştirilebilir:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));

burada N, şamandıralar / çiftler üzerindeki tüm ara hesaplamalar için korunacak hassasiyettir. Bu negatif değerler üzerinde de çalışır. Bu yaklaşımın tüm olasılıklar için matematiksel olarak doğru olup olmadığını bilmiyorum.


0

Bir sayıyı yuvarlamak için basit C kodu:

float n = 3.56;
printf("%.f", n);

Bu Çıktı olacaktır:

4

-1

... ya da herhangi bir kütüphane olmadan eski moda bir şekilde yapabilirsiniz:

float a = 37.777779;

int b = a; // b = 37    
float c = a - b; // c = 0.777779   
c *= 100; // c = 77.777863   
int d = c; // d = 77;    
a = b + d / (float)100; // a = 37.770000;

Tabii ki numaradan ekstra bilgileri kaldırmak istiyorsanız.


-2

bu işlev sayı ve hassasiyeti alır ve yuvarlanmış sayıyı döndürür

float roundoff(float num,int precision)
{
      int temp=(int )(num*pow(10,precision));
      int num1=num*pow(10,precision+1);
      temp*=10;
      temp+=5;
      if(num1>=temp)
              num1+=10;
      num1/=10;
      num1*=10;
      num=num1/pow(10,precision+1);
      return num;
}

noktayı sola kaydırarak ve beşten fazla durumu kontrol ederek kayan nokta sayısını int'e dönüştürür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.