Objective-C: Calculating hourly rate with NSDate and NSDecimalNumber - iphone

I have two instances of NSDate (a start time and an end time) and also one NSDecimalNumber (which represents the amount that was earned over the period of time between the two dates). What I'm trying to do is calculate an hourly rate based on these three variables. I've come up with something that works, but it feels to me a bit excessive and I wanted to know if there was an easier, more efficient way. This is what I am doing now.
Calculating the amount of time between the two NSDates
NSTimeInterval timeInSeconds = [endTime timeIntervalSinceDate:startTime];
Converting this into hours
double hours = timeInSeconds / 3600;
Converting the hours double into an NSNumber
NSNumber *duration = [NSNumber numberWithDouble:hours];
Converting the NSNumber into an NSString
NSLocale *locale = [NSLocale currentLocale];
NSString *durationString = [duration descriptionWithLocale:locale];
Using this string to create an NSDecimalNumber
NSDecimalNumber *decimalDuration = [NSDecimalNumber decimalNumberWithString:durationString locale:locale];
Dividing the original NSDecimalNumber (result) by this new NSDecimalNumber representation of time
NSDecimalNumber *hourlyRate = [result decimalNumberByDividingBy:decimalDuration];
If I'm understanding correctly, the only way to do any math involving my original NSDecimalNumber is to create another instance of NSDecimalNumber, so it is necessary to go through all these steps. I'm worried that I might be losing accuracy or performing unnecessary steps. Thanks for any advice.

Why don't you just use a simple mathematics?
NSTimeInterval timeInSeconds = [endTime timeIntervalSinceDate:startTime];
double hours = timeInSeconds / 3600;
double rate = [result doubleValue];
double hourlyRate = rate / hours;

Related

Is there a better way to calculate a time period?

I have the following code. This is getting calculated in cellForRowAtIndexPath, so I think it could be a little expensive. Is there a better way to calculate a time period?
+(NSString*)toShortTimeIntervalString:(NSString*)sDate
{
NSDateFormatter* df = [[NSDateFormatter alloc]init];
[df setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZ"];
NSDate* date = [df dateFromString:[sDate stringByReplacingOccurrencesOfString:#"Z" withString:#"-0000"]];
[df release];
NSDate* today = [[NSDate alloc] init];
NSDate *d = date; //[_twitter_dateFormatter dateFromString:sDate];
NSTimeInterval interval = [today timeIntervalSinceDate:d];
[today release];
//TODO: added ABS wrapper
double res = 0;
NSString* result;
if(interval > SECONDS_IN_WEEK)
{
res = fabs(interval / SECONDS_IN_WEEK);
result = [NSString stringWithFormat:#"%1.0fw ago", res];
}
else if(interval > SECONDS_IN_DAY)
{
res = fabs(interval / SECONDS_IN_DAY);
result = [NSString stringWithFormat:#"%1.0fd ago", res];
}
else if (interval > SECONDS_IN_HOUR){
res = fabs(interval / SECONDS_IN_HOUR);
result = [NSString stringWithFormat:#"%1.0fh ago", res];
}
else if (interval > SECONDS_IN_MIN) {
res = fabs(interval / SECONDS_IN_MIN);
result = [NSString stringWithFormat:#"%1.0fm ago", res];
}
else
{
interval = fabs(interval);
result = [NSString stringWithFormat:#"%1.0fs ago", interval];
}
return result;
}
This doesn't look too bad, and I doubt you'll be seeing too much of a performance hit from it. To make it more efficient, you might consider only creating one NSDataFormatter (saving it in an instance variable or static variable) and reusing it. Or even better, if you could convert everything to NSDates beforehand, then you wouldn't have to use the formatter every time.
Are you actually seeing any performance issues here, though? Before you try and optimize, you should use Instruments to investigate what's actually taking up time.
Not really, but there are a few categories out there which encapsulate that logic and make it a one line call for you.
Recently I've been in a situation to perform complex calculation (plotting graph for series of years), and I was using NSDate within loops. After analyze with time profiler NSDate, NSDateFormater, NSCalendar was the root cause. I switched to use the C type (time_t + timeinfo) and it drastically improves the speed.
Although they are much faster, they have less functionality, if you need to deal with TimeZone etc, NSDate is probably easier to deal with.

so "lame" - how to have from float = 0.39824702 just float = 0.398?

..so only 3 digit numbers afer "."..
float a = 0.9876543
I would like to have 0.987 only in memory because of transmitting the number over bluetooth..
I'm using iphone SDK..
thank you... :)
A single precision float takes up the same amount of storage whether you're storing an "easy" number like 1.0 or a "complicated" number like 1.23456789. (Likewise for double precision floats -- they're all the same size as each other, although obviously they take more storage space than single precision floats.)
Any network protocol/transport such a Bluetooth involves overheads in just making the thing work, such as headers etc. These overheads mean that the amount of storage you're wanting to save probably just isn't worth the bother - you're talking about shaving a few bytes off a communication which probably a good few multiples bigger than your potential saving anyway.
A more realistic optimization might be to collect some readings and then transmit them all at once, e.g. 32 at once. That makes your "real information" to "protocol overhead" ratio higher.
Here's some advice about optimization you should be aware of:
http://c2.com/cgi/wiki?PrematureOptimization
Don't optimize too early!
You have three answers :
the apple way :
float a = 0.9876543;
NSNumber *number = [NSNumber numberWithFloat: a];
NSNumberFormatter *formatter = [NSNumberFormatter new];
[formatter setMaximumFractionDigits:3];
NSLog(#"%#", [formatter stringFromNumber: number]);
[formatter release], formatter = nil;
the Nerd C way :
float a = 0.9876543;
a = roundf(a*1000)*.001f; // or floatf() if you want truncate and not round number
the apple trick :
float a = 0.9876543;
NSString* tmp = [NSString stringWithFormat:#"%.3f",a];
a = [tmp floatValue];
Good luck
Are you trying to print the number? Anyway, I'm not sure if this is what you're looking for:
float a = 0.9876543;
NSNumber *number = [NSNumber numberWithFloat: a];
NSNumberFormatter *formatter = [NSNumberFormatter new];
[formatter setMaximumFractionDigits:3];
NSLog(#"%#", [formatter stringFromNumber: number]);

NSNumberFormatter question

I'm trying to save a float like so
[NSNumber numberWithFloat:[dosage floatValue]];
and read the value like so:
dosage = [NSString stringWithFormat:#"%1.0f", [prop.dosage floatValue]];
The Problem is that numbers like 0.11 end up being 0
I've read that I can use NSNumberFormatter, but the docs are not very clear on how to set it for rounding.
if its already a string then y do you need to implement stringWithFormat?
for 2 digits floatvalue use %.2f
i.e.
dosage = [NSString stringWithFormat:#"%0.2f", [prop.dosage floatValue]];
HAPPY iCODING...

NSDecimalNumber round long numbers

I'm trying to get NSDecimalNumber to print out large numbers, 15 or more digits. At 15 digits I see 111,111,111,111,111. Above 15 digits I see 1,111,111,111,111,110 even though the number being formatted is 1111111111111111.
An example to illustrate my problem:
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setMaximumSignificantDigits:25];
[formatter setUsesSignificantDigits:true];
NSDecimalNumber* test = [NSDecimalNumber decimalNumberWithString:#"12345678901234567890"];
NSString* output = [formatter stringFromNumber:test];
NSLog( #"num value: %#", test );
NSLog( #"str value: %#", output );
And the output looks like:
2010-09-16 09:24:16.783 SimpleCalc[739:207] num value: 12345678901234567890
2010-09-16 09:24:16.784 SimpleCalc[739:207] str value: 12,345,678,901,234,600,000
What silly thing have I missed?
The problem here is that NSNumberFormatter does not handle NSDecimalNumbers internally, they are converted to double and you are seeing the resulting loss in precision. From the docs:
The representation encompasses integers, floats, and doubles; floats and doubles can be formatted to a specified decimal position.
You should probably be taking a look at the - (NSString *)descriptionWithLocale:(NSDictionary *)locale method on NSDecimalNumber.
Or NSDecimalString(). Take your NSDecimalNumber (e.g. myDecimalNumber), extract the NSDecimal via decimalValue (NSDecimal decimal = [myDecimalNumber decimalValue]) and create an NSString with the NSString *myString = NSDecimalString(decimal) function.
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/index.html#//apple_ref/c/func/NSDecimalString

intValue is missing decimals

I have a price that I need to convert based on the selected currency.
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"currency.plist"];
NSDictionary *plistDictionary = [[NSDictionary dictionaryWithContentsOfFile:finalPath] retain];
int price = [price intValue];
int currencyValue = [[plistDictionary valueForKey:#"EUR"] intValue];
int convertedCurrency = (price / currencyValue);
price is an NSNumber and the valueForKey is also a number from a plist file I have setup with conversion rates.
The problem I am having, is that my price is missing the decimals. Everytime I get the intValue from the price it's just rounded up or down. The same issue exists for the exchange rate I get from the plist.
I have looked into NSNumberFormatter but it won't let me setFormat for the NSNumberFormatter. Any advice, please?
int is an integer type - by definition it does not have a decimal value. Instead try:
float fprice = [price floatValue];
float currencyValue = [[plistDictionary valueForKey:#"EUR"] floatValue];
float convertedCurrency = (fprice / currencyValue);
intValue returns an integer, which (by definition) is rounded to a number without decimals.
You could use doubleValue, which returns a double (which does have the fractional portion) or decimalValue which returns a NSDecimal object.
Take the price string and remove the period. After that convert the NSString to and int, which means you end up with 4235 pennies (or 42 dollars and 35 cents). (Also, make sure that the price string you're getting has two decimal places! Some people are lazy, and output "3.3" for "$3.30".)
NSString *removePeriod = [price stringByReplacingOccurrencesOfString:#"." withString:#""];
int convertedPrice = [removePeriod intValue];
float exchangeRate;
Then get the exchange rates depending on which currency has been selected and use the following code:
int convertedCurrency = round((double)convertedPrice / exchangeRate);
addCurrency = [NSString stringWithFormat: #"%0d.%02d", (convertedCurrency / 100), (convertedCurrency % 100)];
addCurrency is your final price.
To deal with the exact number of deciamlas and if all your currencies have 2 decimal places (e.g not JPY) make all numbers the number of cents
e.g. store 43.35 EUR as 4235.
Then you can use in arithmetic and then just deal with formatting using value/100.0 and NSNumberFormatter