Printf () 'de sondaki sıfırlardan kaçının


107

Printf () işlev ailesi için biçim belirleyicileri bulmaya devam ediyorum. İstediğim, ondalık noktadan sonra maksimum belirli sayıda basamak içeren bir çift (veya kayan nokta) yazdırabilmek. Kullanırsam:

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

alırım

359.013
359.010

Arzu edilenin yerine

359.013
359.01

Birisi bana yardım edebilir mi?


1
Kayan nokta kesinliği gerçekten yuvarlamayı kendiniz yapmanız gerektiği anlamına gelir. R ve Juha'nın cevabının bir varyantını alın (sondaki sıfırlarla pek ilgilenmez) ve düzeltin.
wnoise

Yanıtlar:


88

Bu, normal printfbiçim belirleyicileriyle yapılamaz . Alabileceğiniz en yakın şey:

printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01);  // 359.01

ancak ".6" toplam sayısal genişliktir, bu nedenle

printf("%.6g", 3.01357); // 3.01357

bozar.

Ne yapabilirsiniz yapmaktır sprintf("%.20g")sonra virgülden sonra sadece sahip N karakter dizesi manipüle bir dize tamponuna sayı.

Numaranızın num değişkeninde olduğunu varsayarsak, aşağıdaki işlev ilk Nondalıklar dışında tümünü kaldırır ve ardından sondaki sıfırları (ve hepsi sıfırsa ondalık basamağı) çıkarır .

char str[50];
sprintf (str,"%.20g",num);  // Make the number.
morphNumericString (str, 3);
:    :
void morphNumericString (char *s, int n) {
    char *p;
    int count;

    p = strchr (s,'.');         // Find decimal point, if any.
    if (p != NULL) {
        count = n;              // Adjust for more or less decimals.
        while (count >= 0) {    // Maximum decimals allowed.
             count--;
             if (*p == '\0')    // If there's less than desired.
                 break;
             p++;               // Next character.
        }

        *p-- = '\0';            // Truncate string.
        while (*p == '0')       // Remove trailing zeros.
            *p-- = '\0';

        if (*p == '.') {        // If all decimals were zeros, remove ".".
            *p = '\0';
        }
    }
}

Kesme yönünden memnun değilseniz ( yuvarlamak yerine yuvarlamak yerine 0.12399dönüşür ), aslında tarafından sağlanan yuvarlama olanaklarını kullanabilirsiniz . Genişlikleri dinamik olarak oluşturmak için elden önce sayıyı analiz etmeniz, ardından sayıyı bir dizeye dönüştürmek için bunları kullanmanız yeterlidir:0.1230.124printf

#include <stdio.h>

void nDecimals (char *s, double d, int n) {
    int sz; double d2;

    // Allow for negative.

    d2 = (d >= 0) ? d : -d;
    sz = (d >= 0) ? 0 : 1;

    // Add one for each whole digit (0.xx special case).

    if (d2 < 1) sz++;
    while (d2 >= 1) { d2 /= 10.0; sz++; }

    // Adjust for decimal point and fractionals.

    sz += 1 + n;

    // Create format string then use it.

    sprintf (s, "%*.*f", sz, n, d);
}

int main (void) {
    char str[50];
    double num[] = { 40, 359.01335, -359.00999,
        359.01, 3.01357, 0.111111111, 1.1223344 };
    for (int i = 0; i < sizeof(num)/sizeof(*num); i++) {
        nDecimals (str, num[i], 3);
        printf ("%30.20f -> %s\n", num[i], str);
    }
    return 0;
}

nDecimals()Bu durumda bütün amaç, alan genişliklerini doğru bir şekilde hesaplamak, ardından buna göre bir biçim dizesi kullanarak sayıyı biçimlendirmektir. Test koşum takımı main()bunu eylemde gösterir:

  40.00000000000000000000 -> 40.000
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.010
 359.00999999999999090505 -> 359.010
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

Doğru şekilde yuvarlanmış değere sahip olduğunuzda, bunu bir kez daha geçerek morphNumericString()takip eden sıfırları kaldırabilirsiniz.

nDecimals (str, num[i], 3);

içine:

nDecimals (str, num[i], 3);
morphNumericString (str, 3);

(ya morphNumericStringda sonunda aramak nDecimalsama, bu durumda, muhtemelen ikisini tek bir işlevde birleştiririm) ve sonunda:

  40.00000000000000000000 -> 40
 359.01335000000000263753 -> 359.013
-359.00999000000001615263 -> -359.01
 359.00999999999999090505 -> 359.01
   3.01357000000000008200 -> 3.014
   0.11111111099999999852 -> 0.111
   1.12233439999999995429 -> 1.122

8
Unutmayın, bu yanıt ondalık basamağın .bazı yerel ayarlarda ondalık basamağın aslında ,virgül olduğunu varsayar .

1
Aslında burada küçük bir yazım hatası var - p = strchr (str, '.'); aslında p = strchr (s, '.') olmalıdır; Global var yerine fonksiyon parametresini kullanmak için.
n3wtz

Çok fazla rakam kullanmak, beklenmedik sonuçları tetikleyebilir ... örneğin, 0.1 ile "% .20g" anlamsız değeri gösterir. Kullanıcıları şaşırtmak istemiyorsanız yapmanız gereken, 15 basamaktan başlamak atofve aynı değeri geri alana kadar artırmaya devam etmektir.
6502

@ 6502, bunun nedeni IEEE754'te 0.1 diye bir şey olmamasıdır :-) Ancak, bir soruna neden olmaz (en azından bu değer için), çünkü ortaya çıkan sonuç çıkarıldığında 0.10000000000000000555olacaktır 0.1. En yakın temsili 42.1was gibi biraz daha düşük bir değere sahipseniz sorun gelebilir 42.099999999314159. Bunu gerçekten halletmek istiyorsan, muhtemelen yuvarlamak yerine son basamağı kesmek yerine kaldırılan son rakama göre yuvarlaman gerekir.
paxdiablo

1
@paxdiablo: printfZaten dinamik parametreleri ( *özel karakter) desteklediklerinden, benzer işlevler için dinamik olarak bir biçim dizesi oluşturmaya gerek yoktur : örneğin printf("%*.*f", total, decimals, x);, dinamik olarak belirlenmiş toplam alan uzunluğu ve ondalık sayılarla bir sayı çıkarır.
6502

54

Sondaki sıfırlardan kurtulmak için "% g" biçimini kullanmalısınız:

float num = 1.33;
printf("%g", num); //output: 1.33

Soru biraz açıklığa kavuşturulduktan sonra, sorulan tek şey sıfırları bastırmak değil, çıktıyı üç ondalık basamağa sınırlamak da gerekiyordu. Bunun sadece sprintf format dizeleri ile yapılamayacağını düşünüyorum. Pax Diablo'nun belirttiği gibi , dize manipülasyonu gerekli olacaktır.


MSDN yardım sayfasına bakın
xtofl

@Tomalak: Bu istediğimi yapmıyor. Ondalık noktadan sonra maksimum basamak sayısı belirtebilmek istiyorum. Bu: 1.3347’nin "1.335" ve 1.3397’nin "1.34" olarak basılmasını istiyorum @xtofl: Bunu daha önce kontrol ettim, ancak sorunumun yanıtını hala göremiyorum
Gorpik

3
Yazar stil 'f' istiyor, 'e' stili değil. 'g', 'e' stilini kullanabilir: Dokümantasyondan: Biçim e, dönüşümünden gelen üs -4'ten küçükse veya hassasiyetten büyük veya ona eşitse kullanılır.
Julien Palard

20

Biraz değiştirilmiş R.'nin cevabını beğendim:

float f = 1234.56789;
printf("%d.%.0f", f, 1000*(f-(int)f));

'1000' hassasiyeti belirler.

0.5 yuvarlamaya güç.

DÜZENLE

Tamam, bu cevap birkaç kez düzenlendi ve birkaç yıl önce ne düşündüğümü unuttum (ve başlangıçta tüm kriterleri karşılamadı). İşte yeni bir versiyon (tüm kriterleri dolduran ve negatif sayıları doğru bir şekilde işleyen):

double f = 1234.05678900;
char s[100]; 
int decimals = 10;

sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
printf("10 decimals: %d%s\n", (int)f, s+1);

Ve test durumları:

#import <stdio.h>
#import <stdlib.h>
#import <math.h>

int main(void){

    double f = 1234.05678900;
    char s[100];
    int decimals;

    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf("10 decimals: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" 3 decimals: %d%s\n", (int)f, s+1);

    f = -f;
    decimals = 10;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative 10: %d%s\n", (int)f, s+1);

    decimals = 3;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" negative  3: %d%s\n", (int)f, s+1);

    decimals = 2;
    f = 1.012;
    sprintf(s,"%.*g", decimals, ((int)(pow(10, decimals)*(fabs(f) - abs((int)f)) +0.5))/pow(10,decimals));
    printf(" additional : %d%s\n", (int)f, s+1);

    return 0;
}

Ve testlerin çıktısı:

 10 decimals: 1234.056789
  3 decimals: 1234.057
 negative 10: -1234.056789
 negative  3: -1234.057
 additional : 1.01

Artık tüm kriterler karşılandı:

  • sıfırın arkasındaki maksimum ondalık sayı sabittir
  • sondaki sıfırlar kaldırılır
  • matematiksel olarak doğru mu (değil mi?)
  • ilk ondalık sıfır olduğunda da çalışır (şimdi)

Maalesef bu cevap, sprintfdizgeyi döndürmediği için iki satırlı bir cevaptır .


1
Bu aslında sondaki sıfırları kırpmıyor gibi görünüyor? 1.1000 gibi bir şeyi memnuniyetle tükürebilir.
Dean J

Soru ve cevabım artık orijinal değil ... Daha sonra kontrol edeceğim.
Juha

1
Başarısız test durumları: "% .2g" biçimlendiricili 1.012, 1.01 yerine 1.012 ile sonuçlanır.
wtl

@wtl Güzel yakala. Şimdi düzeltildi.
Juha

@Jeroen: Bence sayılar büyüdüğünde kayan değerlerin de başarısız olduğunu düşünüyorum ... ama temelde uzun, uzun uzun veya int64 şeklinde yazım işe yaramalı.
Juha

2

Ben aralıktaki ilk karakter için dizeyi (başlangıç sağdaki) arama 1için 9(ASCII değeri 49- 57o) null(kümeyi 0) bunun her karakter hakkı - aşağıya bakınız:

void stripTrailingZeros(void) { 
    //This finds the index of the rightmost ASCII char[1-9] in array
    //All elements to the left of this are nulled (=0)
    int i = 20;
    unsigned char char1 = 0; //initialised to ensure entry to condition below

    while ((char1 > 57) || (char1 < 49)) {
        i--;
        char1 = sprintfBuffer[i];
    }

    //null chars left of i
    for (int j = i; j < 20; j++) {
        sprintfBuffer[i] = 0;
    }
}

2

Peki ya bunun gibi bir şey (okuyucu için bir alıştırma olarak bırakılan, hata ayıklama gerektiren yuvarlama hataları ve negatif değerli sorunlar olabilir):

printf("%.0d%.4g\n", (int)f/10, f-((int)f-(int)f%10));

Biraz programatiktir, ancak en azından herhangi bir dizge manipülasyonu yapmanıza neden olmaz.


1

Basit bir çözüm, ancak işi bitirir, bilinen bir uzunluk ve hassasiyet atar ve üstel biçime geçme şansını önler (% g kullandığınızda bu bir risktir):

// Since we are only interested in 3 decimal places, this function
// can avoid any potential miniscule floating point differences
// which can return false when using "=="
int DoubleEquals(double i, double j)
{
    return (fabs(i - j) < 0.000001);
}

void PrintMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%.2f", d);
    else
        printf("%.3f", d);
}

En fazla 2 ondalık sayı istiyorsanız "başka" ekleyin veya kaldırın; 4 ondalık; vb.

Örneğin, 2 ondalık sayı istiyorsanız:

void PrintMaxTwoDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%.1f", d);
    else
        printf("%.2f", d);
}

Alanları hizalı tutmak için minimum genişliği belirtmek istiyorsanız, gerektiği kadar artırın, örneğin:

void PrintAlignedMaxThreeDecimal(double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%7.0f", d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%9.1f", d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%10.2f", d);
    else
        printf("%11.3f", d);
}

Bunu, alanın istenen genişliğini ilettiğiniz bir işleve de dönüştürebilirsiniz:

void PrintAlignedWidthMaxThreeDecimal(int w, double d)
{
    if (DoubleEquals(d, floor(d)))
        printf("%*.0f", w-4, d);
    else if (DoubleEquals(d * 10, floor(d * 10)))
        printf("%*.1f", w-2, d);
    else if (DoubleEquals(d * 100, floor(d* 100)))
        printf("%*.2f", w-1, d);
    else
        printf("%*.3f", w, d);
}

Atın d = 0.0001: o zaman floor(d)olduğu 0fark 0,000001 daha büyük olacak şekilde, yani DoubleEqualsyanlıştır, bu nedenle değil kullanmak "%.0f"belirtici: Eğer gelen ardarda sıfırları göreceksiniz "%*.2f"ya "%*.3f". Yani soruya cevap vermiyor.
Cœur

1

Yayınlanan bazı çözümlerde sorunlar buldum. Bunu yukarıdaki cevaplara göre bir araya getirdim. Benim için çalışıyor gibi görünüyor.

int doubleEquals(double i, double j) {
    return (fabs(i - j) < 0.000001);
}

void printTruncatedDouble(double dd, int max_len) {
    char str[50];
    int match = 0;
    for ( int ii = 0; ii < max_len; ii++ ) {
        if (doubleEquals(dd * pow(10,ii), floor(dd * pow(10,ii)))) {
            sprintf (str,"%f", round(dd*pow(10,ii))/pow(10,ii));
            match = 1;
            break;
        }
    }
    if ( match != 1 ) {
        sprintf (str,"%f", round(dd*pow(10,max_len))/pow(10,max_len));
    }
    char *pp;
    int count;
    pp = strchr (str,'.');
    if (pp != NULL) {
        count = max_len;
        while (count >= 0) {
             count--;
             if (*pp == '\0')
                 break;
             pp++;
        }
        *pp-- = '\0';
        while (*pp == '0')
            *pp-- = '\0';
        if (*pp == '.') {
            *pp = '\0';
        }
    }
    printf ("%s\n", str);
}

int main(int argc, char **argv)
{
    printTruncatedDouble( -1.999, 2 ); // prints -2
    printTruncatedDouble( -1.006, 2 ); // prints -1.01
    printTruncatedDouble( -1.005, 2 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.005, 2 ); // prints 1 (should be 1.01?)
    printTruncatedDouble( 1.006, 2 ); // prints 1.01
    printTruncatedDouble( 1.999, 2 ); // prints 2
    printf("\n");
    printTruncatedDouble( -1.999, 3 ); // prints -1.999
    printTruncatedDouble( -1.001, 3 ); // prints -1.001
    printTruncatedDouble( -1.0005, 3 ); // prints -1.001 (shound be -1?)
    printTruncatedDouble( -1.0004, 3 ); // prints -1
    printf("\n");
    printTruncatedDouble( 1.0004, 3 ); // prints 1
    printTruncatedDouble( 1.0005, 3 ); // prints 1.001
    printTruncatedDouble( 1.001, 3 ); // prints 1.001
    printTruncatedDouble( 1.999, 3 ); // prints 1.999
    printf("\n");
    exit(0);
}

1

Yüksek oy alan çözümlerden bazıları %gdönüşüm tanımlayıcısını önermektedir printf. Bu yanlış çünkü %gbilimsel gösterim üretecek durumlar var . Diğer çözümler, istenen sayıda ondalık basamağı yazdırmak için matematik kullanır.

Bence en kolay çözüm, dönüştürme belirticisi sprintfile kullanmak %fve sondaki sıfırları ve muhtemelen sonuçtan bir ondalık noktayı manuel olarak kaldırmaktır. İşte bir C99 çözümü:

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

char*
format_double(double d) {
    int size = snprintf(NULL, 0, "%.3f", d);
    char *str = malloc(size + 1);
    snprintf(str, size + 1, "%.3f", d);

    for (int i = size - 1, end = size; i >= 0; i--) {
        if (str[i] == '0') {
            if (end == i + 1) {
                end = i;
            }
        }
        else if (str[i] == '.') {
            if (end == i + 1) {
                end = i;
            }
            str[end] = '\0';
            break;
        }
    }

    return str;
}

Basamak ve ondalık ayırıcı için kullanılan karakterlerin mevcut yerel ayara bağlı olduğunu unutmayın. Yukarıdaki kod, bir C veya ABD İngilizcesi yerel ayarını varsayar.


−0.00005 ve 3 ondalık sayı, arzu edilen veya olmayabilen "−0" sonucunu verecektir. Ayrıca, başkalarının rahatlığı için "3" ü bir parametre yapabilir mi?
yunus

0

İşte bir cevap için ilk denemem:

geçersiz
xprintfloat (char * biçimi, float f)
{
  char s [50];
  char * p;

  sprintf (s, format, f);
  için (p = s; * p; ++ p)
    eğer ('.' == * p) {
      while (* ++ p);
      while ('0' == * - p) * p = '\ 0';
    }
  printf ("% s", s);
}

Bilinen hatalar: Biçime bağlı olarak olası arabellek taşması. Eğer "." % f yanlış sonuç olabileceğinden başka bir nedenle mevcut.


Bilinen hatalarınız printf () ile aynıdır, dolayısıyla orada sorun yoktur. Ama benim zaten sahip olduğum şey olan programatik bir çözüm değil, istediğim şeyi yapmama izin veren bir biçim dizesi arıyordum.
Gorpik

0

Neden bunu yapmıyorsun?

double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);

0

Yukarıda hafif varyasyon:

  1. Vaka için süreyi ortadan kaldırır (10000.0).
  2. İlk dönemden sonraki aralar işlenir.

Burada kodlayın:

void EliminateTrailingFloatZeros(char *iValue)
{
  char *p = 0;
  for(p=iValue; *p; ++p) {
    if('.' == *p) {
      while(*++p);
      while('0'==*--p) *p = '\0';
      if(*p == '.') *p = '\0';
      break;
    }
  }
}

Hala taşma potansiyeli var, bu yüzden dikkatli olun; P


0

Kullanman gerektiğini söyleyebilirim printf("%.8g",value);

Eğer kullanırsanız "%.6g"bunu yazdırmalısınız like.32.230210 bazı numaralar için çıktı istenen almazsınız 32.23021ama yazdırır32.2302


-2

Kodunuz, f'den önceki ".3" nedeniyle üç ondalık basamağa yuvarlanır.

printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);

Bu nedenle, ikinci satırı iki ondalık basamağa yuvarladıysanız, bunu şu şekilde değiştirmelisiniz:

printf("%1.3f", 359.01335);
printf("%1.2f", 359.00999);

Bu kod, istediğiniz sonuçları verecektir:

359.013
359.01

* Bunun, halihazırda ayrı satırlara yazdırdığınızı varsaydığına dikkat edin, aksi takdirde aşağıdakiler aynı satıra yazdırılmasını engelleyecektir:

printf("%1.3f\n", 359.01335);
printf("%1.2f\n", 359.00999);

Aşağıdaki program kaynak kodu, bu yanıt için benim testimdi

#include <cstdio>

int main()
{

    printf("%1.3f\n", 359.01335);
    printf("%1.2f\n", 359.00999);

    while (true){}

    return 0;

}

4
Soru, her durum için farklı biçim dizelerine sahip olmak değil, sondaki sıfırları atlayan bir biçim dizesi oluşturmakla ilgiliydi.
Christoph Walesch
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.