Je n'arrête pas de trébucher sur les spécificateurs de format pour la famille de fonctions printf (). Ce que je veux, c'est pouvoir imprimer un double (ou un float) avec un nombre maximum de chiffres après le point décimal. Si j'utilise:
printf("%1.3f", 359.01335);
printf("%1.3f", 359.00999);
Je reçois
359.013
359.010
Au lieu du désiré
359.013
359.01
Quelqu'un peut-il m'aider?
Cela ne peut pas être fait avec les spécificateurs de format printf
normaux. Le plus proche que vous pourriez obtenir serait:
printf("%.6g", 359.013); // 359.013
printf("%.6g", 359.01); // 359.01
mais le ".6" est le total largeur numérique donc
printf("%.6g", 3.01357); // 3.01357
le casse.
Ce que vous pouvez faites, c’est de sprintf("%.20g")
le numéro d’un tampon de chaîne, puis manipulez la chaîne pour n’avoir que N caractères après le point décimal.
En supposant que votre nombre soit dans la variable num, la fonction suivante supprimera toutes les décimales sauf les premières décimales N
, puis supprimera les zéros de fin (et le point décimal s'il s'agit de zéros).
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';
}
}
}
Si vous n'êtes pas satisfait de l'aspect troncature (qui transformerait 0.12399
En 0.123
Plutôt que de l'arrondir à 0.124
), Vous pouvez utiliser les fonctions d'arrondi déjà fournies par printf
. Il vous suffit d'analyser le nombre au préalable pour créer dynamiquement les largeurs, puis de les utiliser pour transformer le nombre en chaîne:
#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;
}
Le but entier de nDecimals()
dans ce cas est de calculer correctement la largeur des champs, puis de formater le nombre à l'aide d'une chaîne de format basée sur celle-ci. Le faisceau de test main()
le montre en action:
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
Une fois que vous avez correctement arrondi la valeur, vous pouvez à nouveau le transmettre à morphNumericString()
pour supprimer les zéros de fin en modifiant simplement:
nDecimals (str, num[i], 3);
dans:
nDecimals (str, num[i], 3);
morphNumericString (str, 3);
(ou en appelant morphNumericString
à la fin de nDecimals
mais, dans ce cas, je combinerais probablement les deux en une seule fonction) et vous obtiendrez:
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
Pour supprimer les zéros de fin, vous devez utiliser le format "% g":
float num = 1.33;
printf("%g", num); //output: 1.33
Après que la question ait été clarifiée un peu, la suppression des zéros n’est pas la seule chose qui a été posée, mais il a également été nécessaire de limiter la sortie à trois décimales. Je pense que cela ne peut pas être fait avec les chaînes de format sprintf seules. Comme Pax Diablo l'a fait remarquer, une manipulation de chaîne serait nécessaire.
J'aime la réponse de R. légèrement modifiée:
float f = 1234.56789;
printf("%d.%.0f", f, 1000*(f-(int)f));
'1000' détermine la précision.
Puissance arrondie à 0,5.
EDIT
Ok, cette réponse a été modifiée plusieurs fois et j’ai perdu la trace de ce que je pensais il ya quelques années (et à l’origine, elle ne remplissait pas tous les critères). Voici donc une nouvelle version (qui remplit tous les critères et gère correctement les nombres négatifs):
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);
Et les cas de test:
#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;
}
Et la sortie des tests:
10 decimals: 1234.056789
3 decimals: 1234.057
negative 10: -1234.056789
negative 3: -1234.057
additional : 1.01
Maintenant, tous les critères sont remplis:
Malheureusement, cette réponse est une double ligne car sprintf
ne renvoie pas la chaîne.
Qu'en est-il quelque chose comme ceci (peut avoir des erreurs d'arrondi et des problèmes de valeur négative nécessitant un débogage, laissé comme exercice pour le lecteur):
printf("%.0d%.4g\n", (int)f/10, f-((int)f-(int)f%10));
C'est un peu programmatique mais au moins, cela ne vous oblige pas à manipuler des chaînes.
Je recherche dans la chaîne (en partant de l'extrême droite) le premier caractère de la plage 1
à 9
_ (Valeur ASCII 49
-57
) puis null
(défini sur 0
) chaque caractère à sa droite - voir ci-dessous:
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;
}
}
Une solution simple mais qui accomplit le travail, attribue une longueur et une précision connues et évite les risques de passer au format exponentiel (ce qui présente un risque lorsque vous utilisez% g):
// 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);
}
Ajoutez ou supprimez "elses" si vous voulez un maximum de 2 décimales; 4 décimales; etc.
Par exemple, si vous vouliez 2 décimales:
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);
}
Si vous souhaitez spécifier la largeur minimale pour garder les champs alignés, incrémentez-le si nécessaire, par exemple:
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);
}
Vous pouvez également convertir cela en une fonction dans laquelle vous passez la largeur souhaitée du champ:
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);
}
J'ai trouvé des problèmes dans certaines des solutions affichées. J'ai mis cela ensemble sur la base des réponses ci-dessus. Il semble fonctionner pour moi.
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);
}
Certaines des solutions très votées suggèrent le %g
spécificateur de conversion de printf
. C'est faux car il y a des cas où %g
produira une notation scientifique. D'autres solutions utilisent les mathématiques pour imprimer le nombre souhaité de chiffres décimaux.
Je pense que la solution la plus simple est d’utiliser sprintf
avec le %f
spécificateur de conversion et pour supprimer manuellement les zéros de fin et éventuellement un point décimal du résultat. Voici une solution C99:
#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;
}
Notez que les caractères utilisés pour les chiffres et le séparateur décimal dépendent des paramètres régionaux en cours. Le code ci-dessus suppose une locale C ou US English.
Légère variation ci-dessus:
Code ici:
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;
}
}
}
Il y a toujours un potentiel de débordement, alors soyez prudent; P
Pourquoi ne pas simplement faire ça?
double f = 359.01335;
printf("%g", round(f * 1000.0) / 1000.0);