[英]C compatible printf output for Java
我想在Java和C中将float / double转换为字符串,这样输出既一致又用户友好 。
通过“用户友好”,我的意思是字符串应该是人类可读和声音:有效数字的最大数量,以及一些适当时自动切换到科学记数法(双倍可以跨越所有有效范围)。
通过“一致”,我的意思是Java和C中的字符串应该完全相同 (如果它们非常罕见,我会容忍一些例外)。
为什么不简单地使用一些printf
格式字符串,如"%.5g"
? 这几乎有效。 但令人遗憾的是,精确字段的含义在Java和C中是完全不同的 。此外,从科学记数到科学记数法的转换不是很一致,甚至格式本身也没有(指数的2或3位数......)。 不同的C编译器有时会产生不同的结果。
"%.5g"
的差异示例
double Java %.5g gcc %.5g tcc %.5g
1234.0 1234.0 1234 1234
123.45678 123.46 123.45678 123.46
0.000123456 0.00012346 0.00012346 0.00012346
0.000000000000123456 1.2346e-13 1.2346e-13 1.2346e-013
我可以使用C或Java(或两者)编写函数,但我想知道是否有人已经处理过这个问题。 我不太关心性能,但是对于C编译器的可移植性是肯定的。
如果你真的想要base-10浮点输出,那么在这里为C的printf
编写一个JNI包装器可能是最容易的。 Java人员决定他们自己需要做printf
。 除了你已经注意到的关于%g
,他们决定改变舍入行为并以奇怪的方式截断输出。 以机智:
System.out.printf("%.5g\n", 1.03125);
System.out.printf("%.5g\n", 1.09375);
1.0313
1.0938
gcc
正确地舍入到偶数:
printf("%.5g\n", 1.03125);
printf("%.5g\n", 1.09375);
1.0312
1.0938
请注意,自1/32 = 0.3125以来,1.03125和1.09375可以完全表示为双精度。
Java的printf%g格式错误地截断了它的输出:
double d = 1;
for (int i = 0; i < 1035; i++) d /= 2;
System.out.printf("%.20g\n%.20a\n", d, d);
2.7161546124360000000e-312
0x0.00080000000000000000p-1022
这是正确的答案:
double d = 1;
for (int i = 0; i < 1035; i++) d /= 2;
printf("%.20g\n%.20a\n", d, d);
2.7161546124355485633e-312
0x0.00080000000000000000p-1022
1.0e-200
是正常的但不能完全代表。 Java假装不注意:
System.out.printf("%.20g\n%.20a\n", 1.0e-200, 1.0e-200);
1.0000000000000000000e-200
0x1.87e92154ef7ac0000000p-665
这是正确的答案:
printf("%.20g\n%.20a\n", 1.0e-200, 1.0e-200);
9.999999999999999821e-201
0x1.87e92154ef7ac0000000p-665
所以你要么在你的printf中接受奇怪的舍入行为,要么背负gcc
和glibc
的工作。 我不建议您自己尝试打印浮点数。 或者您可以使用%a
,AFAIK在Java中完美运行。
好吧,我结束了我自己的功能编码。 在所有double范围内使用gcc和tcc进行测试,得到完全相同的输出(除非极少数非常小的值,小于1E-319)
我发布它以防有人发现它有用。
Java的:
/**
* Returns a double with an adhoc formatting, compatible with its C counterpart
*
* If the absolute value is not too small or too big (thresholdLow-thresholdHigh)
* the floating format is used, elsewhere the scientific.
* In addition
* - trailing zeros in fractional part are removed
* - if the value (or mantisa) is integer, a trailing .0 is always included
* - the exponent in sci notation is two or three digits
* - positive and negative zero returns "0.0"
* - special vals: "NaN" "Infinite" "-Infinite"
*
* Remember to set Locale.setDefault(Locale.US) in your program.
*
* @param v double
* @param formatFloat floating point format, suggested: "%.5f"
* @param formatSci scientific format, must use lowercase 'e' : "%.5e"
* @param thresholdLow
* @param thresholdHigh
* @return formatted string
*/
public static String sprintfDouble(double v, String formatFloat, String formatSci, double thresholdLow,
double thresholdHigh) {
if(v==0.0)
return "0.0"; //dont care about negative zero
if(Double.isInfinite(v) || Double.isNaN(v))
return String.format(formatFloat,v);
boolean neg = false;
if (v < 0) {
v = -v;
neg = true;
}
String e = "";
String res;
if (v > thresholdLow && v < thresholdHigh) {
res = String.format(formatFloat, v);
} else {
res = String.format(formatSci, v);
int sp = res.indexOf('e');
e = res.substring(sp);
res = res.substring(0, sp);
}
if (res.indexOf('.') < 0)
res += "."; // add decimal point if not present
res = res.replaceAll("0+$", ""); // trim trailing zeros
if (res.endsWith("."))
res += "0"; // add traiing zero if nec
res += e;
if (neg)
res = "-" + res;
return res;
}
public static String sprintfDouble5(double v){
return sprintfDouble(v, "%.5f","%.5e",0.01,1000000.0);
}
C:
char * sprintfDouble(char *buf, double v, const char *floatFormat, const char *sciFormat, double thresholdLow, double thresholdHigh) {
char *p;
char *pd; /* pointer to '.' */
char *pe; /* pd=, pe=pointer to 'e' (or null terminator) */
char *buforig;
int trimmed;
if(v != v) { /* nan */
sprintf(buf,"NaN");
return buf;
}
if(v == v && (v - v) != 0.0) { /* infinity */
sprintf(buf, v < 0 ? "-Infinity" :"Infinity");
return buf;
}
if(v==0) { /* positive or negative zero, dont distinguish*/
sprintf(buf, "0.0");
return buf;
}
buforig = buf;
if(v <0) {
v = -v;
buf[0] = '-';
buf++;
}
if( v > thresholdLow && v < thresholdHigh ) {
sprintf(buf,floatFormat, v);
pe = buf+strlen(buf);
pd = (char *) strchr(buf,'.');
if(pd == NULL) { /* no decimal point? add it */
pd = pe;
*pe++ = '.';
*pe++ = '0';
*pe = 0;
}
} else {
sprintf(buf,sciFormat, v);
pe = (char *)strchr(buf,'e');
pd = (char *)strchr(buf,'.');
if(pd ==NULL) { /* no decimal point with scientific notation? rare but... */
p= buf+ strlen(buf);
while(p>=pe) {
*p = *(p-2);
p--;
}
pd = pe;
*pe++ = '.';
*pe++ = '0';
*pe = 0;
}
/* three digits exponent with leading zero? trim it */
if( (*(pe+2) == '0' ) && ( strlen(buf) - (pe-buf))==5) {
*(pe+2)=*(pe+3);
*(pe+3)=*(pe+4);
*(pe+4)=*(pe+5);
}
} /* now trim trailing zeros */
trimmed = 0;
p=pe-1;
while(*p =='0' ) {
p--;
trimmed++;
}
if(*p=='.') {
trimmed--; // dont trim the zero after the decimal point
p++;
}
if(trimmed>0) {
p = pe;
while(1) {
*(p-trimmed) = *p;
if(*p==0) break;
p++;
}
}
return buforig;
}
char * sprintfDouble5(char *buf,double v) {
return sprintfDouble(buf, v, "%.5f", "%.5e", 0.01, 1000000.0);
}
测试代码。
Java的
static void test() {
Locale.setDefault(Locale.US);
double start = 1.0;
double x=start;
for(int i=0;i<367;i++) {
System.out.println(sprintfDouble5(x));
x*= -7.0;
}
x=start;
for(int i=0;i<6;i++) {
System.out.println(sprintfDouble5(x));
x/= -5;
}
for(int i=0;i<200;i++) {
System.out.println(sprintfDouble5(x));
x/= -42.01;
}
x=Math.PI*0.0000001;
for(int i=0;i<20;i++) {
System.out.println(sprintfDouble5(x));
x*=10;
}
System.out.println(sprintfDouble5(0.0));
System.out.println(sprintfDouble5(-0.0));
System.out.println(sprintfDouble5(0.0/0.0));
}
C:
void test1() {
char buf[64];
double start,x;
int i;
start = 1.0;
x = start;
for(i=0;i<367;i++) {
printf("%s\n",sprintfDouble5(buf,x));
x *= -7.0;
}
x = start;
for(i=0;i<6;i++) {
printf("%s\n",sprintfDouble5(buf,x));
x /= -5;
}
for(i=0;i<200;i++) {
printf("%s\n",sprintfDouble5(buf,x));
x/= -42.01;
}
x = atan(1.0) * 4 * 0.0000001; /* PI */
for(i=0;i<20;i++) {
printf("%s\n",sprintfDouble5(buf,x));
x *= 10;
}
printf("%s\n",sprintfDouble5(buf,0.0));
printf("%s\n",sprintfDouble5(buf,-0.0));
printf("%s\n",sprintfDouble5(buf,0.0/0.0));
}
这段代码
#include <stdio.h>
int main() {
double v;
char format[] = "%.5g\n";
v = 1234.0;
printf(format, v);
v = 123.45678;
printf(format, v);
v = 0.000123456;
printf(format, v);
v = 0.000000000000123456;
printf(format, v);
}
给我
1234
123.46
0.00012346
1.2346e-13
和这段代码
public class App13749802 {
/**
* @param args
*/
public static void main(String[] args) {
double v;
String format = "%.5g";
v = 1234.0;
System.out.println(String.format(format, v));
v = 123.45678;
System.out.println(String.format(format, v));
v = 0.000123456;
System.out.println(String.format(format, v));
v = 0.000000000000123456;
System.out.println(String.format(format, v));
}
}
给我
1234,0
123,46
0,00012346
1,2346e-13
逗号是因为我的区域设置。 所以只有一个区别。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.