限制浮点数在显示时的小数位数和总位数

8
我需要以最高效的方式在有限宽度的区域中打印浮点数值。我正在使用一个NSNumberFormatter,并将小数点后的两个数字设置为默认值,这样当我有一个像234.25这样的数字时,它会原样打印: 234.25。但是当我有1234.25时,我希望它被打印为:1234.3,而且如果11234.25应该打印为11234
我需要保留小数点后最多两位数字,并且如果有小数点后的数字,总共最多只能有五位数字,但如果整数部分有更多数字,则应打印更多数字。
我在NSNumberFormatter中没有看到限制数字总数的能力。这是否意味着我应该编写自己的函数以按此方式格式化数字?如果是这样,那么获取数字整数和小数部分的数字计数的正确方法是什么?我还更喜欢使用CGFLoat而不是NSNumber,以避免额外的类型转换。
4个回答

15

你正在寻找“最大有效数字”和“最大小数位数”的组合,以及特定的舍入行为。 NSNumberFormatter 可以胜任此任务:

float twofortythreetwentyfive = 234.25;
float onetwothreefourtwentyfive = 1234.25;
float eleventwothreefourtwentyfive = 11234.25;

NSNumberFormatter * formatter =  [[NSNumberFormatter alloc] init];
[formatter setUsesSignificantDigits:YES];
[formatter setMaximumSignificantDigits:5];
[formatter setMaximumFractionDigits:2];
[formatter setRoundingMode:NSNumberFormatterRoundCeiling];

NSLog(@"%@", [formatter stringFromNumber:[NSNumber numberWithFloat:twofortythreetwentyfive]]);
NSLog(@"%@", [formatter stringFromNumber:[NSNumber numberWithFloat:onetwothreefourtwentyfive]]);
NSLog(@"%@", [formatter stringFromNumber:[NSNumber numberWithFloat:eleventwothreefourtwentyfive]]);

结果:

2012年4月26日16:32:04.481 SignificantDigits[11565:707] 234.25
2012年4月26日16:32:04.482 SignificantDigits[11565:707] 1234.3
2012年4月26日16:32:04.483 SignificantDigits[11565:707] 11235


谢谢。这看起来非常简单。但是,如果我有一个数字:211234.25,它将返回211230。但我希望它返回211234,以便它只舍入浮点部分,整数部分应保持不变。使用NSNumberFormatter仍然可能吗? - BartoNaz
所以我希望我的打印数字的最佳长度不超过5位数。因此,如果它有<=3个整数位,我会完全打印它。如果有4个或更多整数位,则我会截断小数部分。整数位不做任何更改。只有最后一位数字可以根据小数部分进行更改。 - BartoNaz
1
但是这样你就没有固定的宽度了。老实说,我不确定如何让 NSNumberFormatter 实现这一点;从我的经验来看,如果输入的 log10(大约等于整数部分长度减1)大于5,则可以截断输入。 - jscs
抱歉,我应该表述得更清楚。所谓宽度是指以像素为单位的宽度。因此,我想要尽可能有效地利用给定的像素区域来显示数字。 - BartoNaz
哦,我明白了。如果你想使用NSNumberFormatter,每次输入的数字太长时,你可能需要稍微调整一下设置。否则,我猜类似于Dr. Kameleon提供的自定义函数可能是个方法。 - jscs

1

代码:

#define INTPARTSTR(X) [NSString stringWithFormat:@"%d",(int)X]
#define DECPARTSTR(X) [NSString stringWithFormat:@"%d",(int)(((float)X-(int)X)*100)]

- (NSString*)formatFloat:(float)f
{
    NSString* result;

    result = [NSString stringWithFormat:@"%.2f",f];

    if ([DECPARTSTR(f) isEqualToString:@"0"]) return INTPARTSTR(f);
    if ([INTPARTSTR(f) length]==5) return INTPARTSTR(f);

    if ([result length]>5)
    {
        int diff = (int)[result length]-7;

        NSString* newResult = @"";

        for (int i=0; i<[result length]-diff-1; i++)
            newResult = [newResult stringByAppendingFormat:@"%c",[result characterAtIndex:i]];

        return newResult;
    }


    return result;
}

测试一下:

- (void)awakeFromNib
{
    NSLog(@"%@",[self formatFloat:234.63]);
    NSLog(@"%@",[self formatFloat:1234.65]);
    NSLog(@"%@",[self formatFloat:11234.65]);
    NSLog(@"%@",[self formatFloat:11234]);
}

输出:

2012-04-26 19:27:24.429 newProj[1798:903] 234.63
2012-04-26 19:27:24.432 newProj[1798:903] 1234.6
2012-04-26 19:27:24.432 newProj[1798:903] 11234
2012-04-26 19:27:24.432 newProj[1798:903] 11234

谢谢。这个实现看起来非常通用,但我担心效率问题。除了计算字符串长度之外,是否可以使用比NSString更高效的方法来计算数字中的位数?我将在程序运行的所有时间内每帧动画连续进行此转换。这不会很困难吗? - BartoNaz
2
@BartoNaz:一个数的以10为底的对数大约等于它整数位数减1的长度:log10f(11123.25) -> 4.046221 - jscs
是的,对数是个好主意。我会尝试做出类似@Dr.Kameleon提出的东西,但是使用对数而不是NSString。我认为这样应该会更快。 - BartoNaz
1
@BartoNaz 刚刚看了其他评论,我也认为日志似乎是一个非常有趣的补充... :-) - Dr.Kameleon

0
这是我在代码中的实现方式。我不知道它有多高效,但希望不会太差。
所以我创建了一个全局的NSNumberFormatter。
NSNumberFormatter* numFormatter; 

并在某处初始化它:

numFormatter=[[NSNumberFormatter alloc]init];

然后我使用以下函数格式化数字:

- (NSString*)formatFloat:(Float32)number withOptimalDigits:(UInt8)optimalDigits maxDecimals:(UInt8)maxDecimals
{
    NSString* result;
    UInt8 intDigits=(int)log10f(number)+1;
    NSLog(@"Formatting %.5f with maxDig: %d maxDec: %d intLength: %d",number,optimalDigits,maxDecimals,intDigits);  

    numFormatter.maximumFractionDigits=maxDecimals;
    if(intDigits>=optimalDigitis-maxDecimals) {
        numFormatter.usesSignificantDigits=YES;
        numFormatter.maximumSignificantDigits=(intDigits>optimalDigits)?intDigits:optimalDigits;
    } else {
        numFormatter.usesSignificantDigits=NO;
    }
    result = [numFormatter stringFromNumber:[NSNumber numberWithFloat:number]];

    return result;
}

-1
这是在iOS 8上使用NSNumberForamtter的maximumFractionDigits和maximumSignificantDigits一起时出现的错误吗?
        NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
        formatter.maximumFractionDigits = 2;  
        formatter.maximumSignificantDigits = 3;
        NSLog(@"%@", [formatter stringFromNumber:@(0.3333)]); // output 0.333 expected 0.33

如果我只使用maximumFractionDigits,它可以正常工作。

        NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
        formatter.maximumFractionDigits = 2;
        NSLog(@"%@", [formatter stringFromNumber:@(0.3333)]); // output expected .33

NSNumberFormatter最大小数位数和最大有效数字位数问题


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接