objective-cnsstringnsnumberzeronsnumberformatter

Prevent small negative numbers printing as "-0"


If I do the following in Objective-C:

NSString *result = [NSString stringWithFormat:@"%1.1f", -0.01];

It will give result @"-0.0"

Does anybody know how I can force a result @"0.0" (without the "-") in this case?

EDIT: I tried using NSNumberFormatter, but it has the same issue. The following also produces @"-0.0":

double value = -0.01;
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:1];
[numberFormatter setMinimumFractionDigits:1];
NSString *result = [numberFormatter stringFromNumber:[NSNumber numberWithDouble:value]];

Solution

  • I wanted a general solution, independent of the configuration of the number formatter.

    I've used a category to add the functionality to NSNumberFormater;

    @interface NSNumberFormatter (PreventNegativeZero)
    - (NSString *)stringFromNumberWithoutNegativeZero:(NSNumber *)number;
    @end
    

    With the implementation:

    @implementation NSNumberFormatter (PreventNegativeZero)
    
    - (NSString *)stringFromNumberWithoutNegativeZero:(NSNumber *)number
    {
        NSString *const string = [self stringFromNumber: number];
        NSString *const negZeroString = [self stringFromNumber: [NSNumber numberWithFloat: -0.0f]];
    
        if([string isEqualToString: negZeroString])
        {
            NSString *const posZeroString = [self stringFromNumber: [NSNumber numberWithFloat: 0.0]];
            return posZeroString;
        }
        
        return string;
    }
    
    @end
    

    How it works

    The key feature is to ask the number formatter how it will format -0.0f (i.e., floating point minus zero) as an NSString so that we can detect this and take remedial action.

    Why do this? Depending on the formatter configuration, -0.0f could be formatted as: @"-0", @"-0.0", @"-000", @"-0ºC", @"£-0.00", @"----0.0", @"(0.0)", @"😡𝟘.⓪零" really, pretty much anything. So, we ask the formatter how it would format -0.0f using the line: NSString *const negZeroString = [self stringFromNumber: [NSNumber numberWithFloat: -0.0f]];

    Armed with the undesired -0.0f string, when an arbitrary input number is formatted, it can be tested to see if it is matches the undesirable -0.0f string.

    The second important feature is that the number formatter is also asked to supply the replacement positive zero string. This is necessary so that as before, its formatting is respected. This is done with the line: [self stringFromNumber: [NSNumber numberWithFloat: 0.0]]

    An optimisation that doesn't work

    It's tempting to perform a numerical test yourself for whether the input number will be formatted as the -0.0f string, but this is extremely non trivial (ie, basically impossible in general). This is because the set of numbers that will format to the -0.0f string depend on the configuration of the formatter. If if happens to be rounding to the nearest million, then -5,000f as an input would be formatted as the -0.0f string.

    An implementation error to avoid

    When input that formats to the -0.0f string is detected, a positive zero equivalent output string is generated using [self stringFromNumber: [NSNumber numberWithFloat: 0.0]]. Note that, specifically:

    Negating an input of -0.1f would result in formatting 0.1f. Depending on the formatter behaviour, this could be rounded up and result in @"1,000", which you don't want.

    Final Note

    For what it's worth, the approach / pattern / algorithm used here will translate to other languages and different string formatting APIs.