Làm cách nào tôi có thể làm tròn một giá trị float (chẳng hạn như 37.777779) đến hai chữ số thập phân (37,78) trong C?
Làm cách nào tôi có thể làm tròn một giá trị float (chẳng hạn như 37.777779) đến hai chữ số thập phân (37,78) trong C?
Câu trả lời:
Nếu bạn chỉ muốn làm tròn số cho mục đích đầu ra, thì "%.2f"
chuỗi định dạng thực sự là câu trả lời đúng. Tuy nhiên, nếu bạn thực sự muốn làm tròn giá trị dấu phẩy động để tính toán thêm, một cái gì đó giống như các công việc sau:
#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 */
Lưu ý rằng có ba quy tắc làm tròn khác nhau mà bạn có thể muốn chọn: làm tròn xuống (nghĩa là cắt ngắn sau hai chữ số thập phân), làm tròn đến gần nhất và làm tròn lên. Thông thường, bạn muốn vòng đến gần nhất.
Như một số người khác đã chỉ ra, do sự kỳ quặc của biểu diễn dấu phẩy động, các giá trị tròn này có thể không chính xác là giá trị thập phân "rõ ràng", nhưng chúng sẽ rất gần nhau.
Để biết thêm (nhiều!) Thông tin thêm về làm tròn, và đặc biệt là các quy tắc ngắt kết nối để làm tròn đến gần nhất, hãy xem bài viết Wikipedia về Làm tròn .
doubles
quá bằng cách nào đó? Dường như không làm công việc tôi muốn :( (sử dụng floor
và ceil
).
Sử dụng % .2f trong printf. Nó chỉ in 2 điểm thập phân.
Thí dụ:
printf("%.2f", 37.777779);
Đầu ra:
37.77
float
phạm vi như val * 100
có thể tràn.
Giả sử bạn đang nói về việc làm tròn giá trị để in, thì câu trả lời của Andrew Coleson và AraK là chính xác:
printf("%.2f", 37.777779);
Nhưng lưu ý rằng nếu bạn muốn làm tròn số thành chính xác 37,78 để sử dụng nội bộ (ví dụ: để so sánh với giá trị khác), thì đây không phải là ý tưởng hay, do cách thức hoạt động của các số dấu phẩy động: bạn thường không muốn thực hiện so sánh đẳng thức cho dấu phẩy động, thay vào đó hãy sử dụng giá trị đích +/- giá trị sigma. Hoặc mã hóa số dưới dạng một chuỗi với độ chính xác đã biết và so sánh số đó.
Xem liên kết trong câu trả lời của Greg Hewgill cho một câu hỏi liên quan , cũng bao gồm lý do tại sao bạn không nên sử dụng dấu phẩy động để tính toán tài chính.
printf("%.*f", (int)precision, (double)number);
Còn cái này thì sao:
float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);
printf("%.2f", 37.777779);
Nếu bạn muốn ghi vào chuỗi C:
char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);
Không có cách nào để làm tròn số này float
đến số khác float
vì số được làm tròn float
có thể không thể biểu thị được (giới hạn số dấu phẩy động). Chẳng hạn, giả sử bạn làm tròn 37.777779 đến 37,78, nhưng số đại diện gần nhất là 37,781.
Tuy nhiên, bạn có thể "làm tròn" a float
bằng cách sử dụng hàm chuỗi định dạng.
float
đến n vị trí thập phân và sau đó mong đợi kết quả luôn có n vị trí thập phân. Bạn vẫn sẽ nhận được một float
, chỉ không phải là người bạn mong đợi.
Ngoài ra, nếu bạn đang sử dụng C ++, bạn có thể tạo một hàm như thế này:
string prd(const double x, const int decDigits) {
stringstream ss;
ss << fixed;
ss.precision(decDigits); // set # places after decimal
ss << x;
return ss.str();
}
Sau đó, bạn có thể xuất bất kỳ gấp đôi nào myDouble
với các n
vị trí sau dấu thập phân với mã như sau:
std::cout << prd(myDouble,n);
Bạn vẫn có thể sử dụng:
float ceilf(float x); // don't forget #include <math.h> and link with -lm.
thí dụ:
float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;
Trong C ++ (hoặc trong C với phôi kiểu C), bạn có thể tạo hàm:
/* 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;
}
Sau đó std::cout << showDecimals(37.777779,2);
sẽ sản xuất: 37,78.
Rõ ràng là bạn không thực sự cần phải tạo cả 5 biến trong hàm đó, nhưng tôi để chúng ở đó để bạn có thể thấy logic. Có thể có các giải pháp đơn giản hơn, nhưng điều này hiệu quả với tôi - đặc biệt là vì nó cho phép tôi điều chỉnh số chữ số sau chữ số thập phân khi tôi cần.
Luôn luôn sử dụng printf
họ các chức năng cho việc này. Ngay cả khi bạn muốn nhận giá trị dưới dạng float, tốt nhất bạn nên sử dụng snprintf
để lấy giá trị làm tròn dưới dạng chuỗi và sau đó phân tích lại bằng 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;
}
Tôi nói điều này bởi vì cách tiếp cận được hiển thị bởi câu trả lời được bình chọn hàng đầu hiện tại và một số cách khác ở đây - nhân với 100, làm tròn đến số nguyên gần nhất, sau đó chia cho 100 lần nữa - bị sai theo hai cách:
Để minh họa loại lỗi đầu tiên - hướng làm tròn đôi khi bị sai - hãy thử chạy chương trình này:
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);
}
Bạn sẽ thấy đầu ra này:
x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406
Lưu ý rằng giá trị chúng tôi bắt đầu bằng nhỏ hơn 0,015 và vì vậy câu trả lời đúng về mặt toán học khi làm tròn nó đến 2 chữ số thập phân là 0,01. Tất nhiên, 0,01 không thể biểu diễn chính xác như một đôi, nhưng chúng tôi hy vọng kết quả của chúng tôi sẽ là gấp đôi gần nhất với 0,01. Sử dụng snprintf
cho chúng ta kết quả đó, nhưng sử dụng round(100 * x) / 100
cho chúng ta 0,02, đó là sai. Tại sao? Bởi vì 100 * x
cho chúng tôi chính xác 1,5 như kết quả. Nhân với 100 do đó thay đổi hướng chính xác để làm tròn.
Để minh họa loại lỗi thứ hai - kết quả đôi khi bị sai do * 100
và / 100
không thực sự là nghịch đảo của nhau - chúng ta có thể thực hiện một bài tập tương tự với một số rất lớn:
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);
}
Số của chúng tôi bây giờ thậm chí không có một phần nhỏ; nó là một giá trị nguyên, chỉ được lưu trữ với loại double
. Vì vậy, kết quả sau khi làm tròn nó sẽ là cùng một số chúng ta bắt đầu, phải không?
Nếu bạn chạy chương trình trên, bạn sẽ thấy:
x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0
Giáo sư. snprintf
Phương pháp của chúng tôi trả về kết quả đúng một lần nữa, nhưng cách tiếp cận nhân-sau-vòng-sau-sau-chia-thất bại. Đó là bởi vì giá trị chính xác về mặt toán học của 8631192423766613.0 * 100
,, 863119242376661300.0
không thể biểu diễn chính xác như một đôi; giá trị gần nhất là 863119242376661248.0
. Khi bạn chia lại cho 100, bạn nhận được8631192423766612.0
- một số khác với số bạn đã bắt đầu.
Hy vọng rằng đó là một minh chứng đầy đủ rằng việc sử dụng roundf
để làm tròn đến một số vị trí thập phân bị hỏng và snprintf
thay vào đó bạn nên sử dụng . Nếu điều đó cảm thấy như một vụ hack khủng khiếp đối với bạn, có lẽ bạn sẽ yên tâm bởi kiến thức rằng về cơ bản đó là những gì CPython làm .
Sử dụng float roundf(float x)
.
"Các hàm làm tròn làm tròn đối số của chúng thành giá trị nguyên gần nhất ở định dạng dấu phẩy động, làm tròn các trường hợp nửa chừng từ 0, bất kể hướng làm tròn hiện tại." C11dr7.12.9.5
#include <math.h>
float y = roundf(x * 100.0f) / 100.0f;
Tùy thuộc vào float
việc triển khai của bạn , những con số có vẻ như là một nửa. như dấu phẩy động thường là định hướng cơ sở 2. Hơn nữa, làm tròn chính xác đến gần nhất 0.01
trong tất cả các trường hợp "nửa đường" là thách thức nhất.
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
Mặc dù "1.115" là "nửa đường" giữa 1.11 và 1.12, khi được chuyển đổi thành float
, giá trị 1.115000009537...
này không còn là "nửa đường", mà gần hơn với 1.12 và làm tròn đến gần nhất float
của1.120000004768...
"1.125" là "nửa đường" trong khoảng từ 1.12 đến 1.13, khi được chuyển đổi thành float
, giá trị chính xác 1.125
và là "nửa đường". Nó làm tròn tới 1,13 do liên kết với quy tắc chẵn và làm tròn đến gần nhất float
của1.129999995232...
Mặc dù "1.135" là "nửa đường" trong khoảng từ 1.13 đến 1.14, khi được chuyển đổi thành float
, giá trị 1.134999990463...
này không còn là "nửa đường", mà gần hơn với 1.13 và làm tròn đến gần nhất float
của1.129999995232...
Nếu mã được sử dụng
y = roundf(x*100.0f)/100.0f;
Mặc dù "1,135" là "một nửa chiều" giữa 1,13 và 1,14, khi chuyển đổi sang float
, giá trị là 1.134999990463...
và không còn là "một nửa chiều", nhưng gần gũi hơn với 1,13 nhưng không đúng vòng tới float
của 1.139999985695...
do độ chính xác hạn chế hơn float
so với double
. Giá trị không chính xác này có thể được xem là chính xác, tùy thuộc vào mục tiêu mã hóa.
Tôi đã tạo macro này để làm tròn số float. Thêm nó trong tiêu đề / là tập tin của bạn
#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))
Đây là một ví dụ:
float x = ROUNDF(3.141592, 100)
x bằng 3,14 :)
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));
}
Đây n
là số thập phân
thí dụ:
double d = 100.23456;
printf("%f", f_round(d, 4));// result: 100.2346
printf("%f", f_round(d, 2));// result: 100.23
dval
là 3 lớn) khối lạ if
/ else
khối nơi bạn thực hiện chính xác điều tương tự trong mỗi nhánh và 4) việc sử dụng quá mức sprintf
để xây dựng trình xác định định dạng cho sprintf
cuộc gọi thứ hai ; đơn giản hơn là chỉ sử dụng .*
và chuyển giá trị kép và số vị trí thập phân làm đối số cho cùng một sprintf
cuộc gọi.
#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))
a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430
Trước tiên hãy để tôi biện minh cho lý do của mình để thêm một câu trả lời khác cho câu hỏi này. Trong một thế giới lý tưởng, làm tròn không thực sự là một vấn đề lớn. Tuy nhiên, trong các hệ thống thực, bạn có thể cần phải giải quyết một số vấn đề có thể dẫn đến làm tròn có thể không như bạn mong đợi. Ví dụ: bạn có thể thực hiện các tính toán tài chính trong đó kết quả cuối cùng được làm tròn và hiển thị cho người dùng dưới dạng 2 chữ số thập phân; các giá trị tương tự này được lưu trữ với độ chính xác cố định trong cơ sở dữ liệu có thể bao gồm hơn 2 vị trí thập phân (vì nhiều lý do; không có số vị trí tối ưu để giữ ... phụ thuộc vào các tình huống cụ thể mà mỗi hệ thống phải hỗ trợ, ví dụ: các mặt hàng nhỏ có giá là các phân số của một xu trên mỗi đơn vị); và, tính toán dấu phẩy động được thực hiện trên các giá trị trong đó kết quả là cộng / trừ epsilon. Tôi đã phải đối mặt với những vấn đề này và phát triển chiến lược của riêng tôi trong những năm qua. Tôi sẽ không tuyên bố rằng tôi đã đối mặt với mọi kịch bản hoặc có câu trả lời tốt nhất, nhưng dưới đây là một ví dụ về cách tiếp cận của tôi cho đến nay khắc phục những vấn đề này:
Giả sử 6 vị trí thập phân được coi là đủ chính xác để tính toán trên phao / đôi (một quyết định tùy ý cho ứng dụng cụ thể), sử dụng hàm / phương pháp làm tròn sau:
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;
}
}
Làm tròn đến 2 vị trí thập phân để trình bày kết quả có thể được thực hiện như sau:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));
Cho val = 6.825
, kết quả là6.83
như mong đợi.
Đối với val = 6.824999
, kết quả là 6.82
. Ở đây giả định là việc tính toán dẫn đến kết quả chính xác6.824999
và vị trí thập phân thứ 7 bằng không.
Đối với val = 6.8249999
, kết quả là 6.83
. Vị trí thập phân thứ 7 9
trong trường hợp này làm cho Round(val,6)
hàm đưa ra kết quả mong đợi. Trong trường hợp này, có thể có bất kỳ số lượng dấu9
.
Đối với val = 6.824999499999
, kết quả là 6.83
. Làm tròn đến vị trí thập phân thứ 8 là bước đầu tiên, nghĩa là Round(val,8)
xử lý một trường hợp khó chịu, theo đó kết quả điểm nổi được tính toán sẽ được tính 6.8249995
, nhưng được biểu thị bên trong như6.824999499999...
.
Cuối cùng, ví dụ từ câu hỏi ... val = 37.777779
kết quả là37.78
.
Cách tiếp cận này có thể được khái quát hơn như:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));
Trong đó N là độ chính xác được duy trì cho tất cả các tính toán trung gian trên phao / đôi. Điều này hoạt động trên các giá trị tiêu cực là tốt. Tôi không biết cách tiếp cận này có đúng về mặt toán học cho tất cả các khả năng hay không.
Mã C đơn giản để làm tròn số:
float n = 3.56;
printf("%.f", n);
Điều này sẽ xuất ra:
4
... Hoặc bạn có thể làm theo cách cũ mà không cần bất kỳ thư viện nào:
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;
Điều đó tất nhiên nếu bạn muốn loại bỏ thông tin bổ sung từ số.
hàm này lấy số và độ chính xác và trả về số làm tròn
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;
}
nó chuyển đổi số dấu phẩy động thành int bằng cách dịch chuyển trái điểm và kiểm tra điều kiện lớn hơn năm.
float
(vàdouble
) không phải là dấu phẩy động thập phân - chúng là dấu phẩy động nhị phân - vì vậy làm tròn đến vị trí thập phân là vô nghĩa. Bạn có thể làm tròn đầu ra, tuy nhiên.