I am learning Objective-C and iOS development by making a simple tip calculator. However, the issue I am having is when I try to calculate the tip. This is simple math (tip percent / total bill) * 100. This is exactly what I am doing, but I am really confused as to why my output is wrong.
This is the method in my ViewController.m file that is giving me issues
- (IBAction)doCalculate:(id)sender {
NSInteger totalBillAmount = self.inputTotalBill.text.intValue;
NSLog(#"input total bill: %i", totalBillAmount);
NSInteger tipPercent = self.inputTip.text.intValue;
NSLog(#"input tip percent: %i", tipPercent);
NSInteger tipAmount = (tipPercent / totalBillAmount) * 100;
NSLog(#"tip amount: %i", tipAmount);
NSInteger billAmount = totalBillAmount + tipAmount;
NSLog(#"total bill: %i", billAmount);
// Set labels accordingly
self.labelTipAmount.text = [NSString stringWithFormat:#"%i", tipAmount];
self.labelBillAmount.text = [NSString stringWithFormat:#"%i", billAmount];
}
And this is my output:
2016-02-28 01:39:36.283 conversion[1533:58347] input total bill: 100
2016-02-28 01:39:36.285 conversion[1533:58347] input tip percent: 15
2016-02-28 01:39:36.285 conversion[1533:58347] tip amount: 0
2016-02-28 01:39:36.285 conversion[1533:58347] total bill: 100
I am really confused so any help is appreciated, thanks!
Computer numbers does not behave like numbers you learned at school. Integers only use integer arithmetic, and then integer division (look on the web).
4/100 (as an integer division) gives 0 (remember Euclidian division?) If you want to make more natural computations, use floats or doubles (but they will surprise you even more later!).
When you divide two NSIntegers, the result is one NSInteger. If that fraction is <1 and >0 then the output is 0. Using NSInteger might not be the best option here if you want it to be simple.
NSInteger tipAmount = (tipPercent * totalBillAmount) / 100;
NSLog(#"tip amount: %i", tipAmount); // tip amount: 0
If you used a float, it would be a whole lot cleaner:
float tipAmount = (tipPercent * totalBillAmount) / 100;
NSLog(#"tip amount: $%.02f", tipAmount); // tip amount: $15.00
However, using floats for currency can be very bad. So, it would be a more sage decision to use NSInteger to keep track of the smallest unit of currency instead. For the USD this is $0.001, or one tenth of a cent.
This means, when someone enters the bill total, let's say $100.00, you would record that value as 100000.
Then, to calculate 15%, you would multiply the bill by 15 and then divide by 100.
NSInteger tipAmount = (tipPercent * totalBillAmount) / 100;
NSLog(#"tip amount: %i", tipAmount); // tip amount: 15000
To show the user again, I would use a method like the following to convert from tenth cent units to a formatted string for dollars:
- (NSString *)tenthCentToDollarString:(NSInteger)tenthCents {
if (tenthCents >= 0) {
NSInteger roundedCents = (tenthCents + 5) / 10;
if (roundedCents < 10) {
return [NSString stringWithFormat:#"$0.0%zd", roundedCents];
}
if (roundedCents < 100) {
return [NSString stringWithFormat:#"$0.%zd", roundedCents];
}
NSInteger cents = roundedCents % 100;
NSInteger dollars = roundedCents / 100;
if (cents < 10) {
return [NSString stringWithFormat:#"$%zd.0%zd", dollars, cents];
}
return [NSString stringWithFormat:#"$%zd.%zd", dollars, cents];
}
// Dollar amount is negative
NSInteger positiveTenthCents = ABS(tenthCents);
NSString *dollarString = [self tenthCentToDollarString:positiveTenthCents];
return [NSString stringWithFormat:#"-%#", dollarString];
}
A couple issues: You should be doing tipAmount = (tipPercent / 100) * totalBillAmount and cast to doubles because NSInts can't do fractions.
Related
I tried this:
NSInteger numberFinal = 100000000000000000 + ((float)arc4random() / UINT32_MAX) * (999999999999999999 - 100000000000000000);
but it returns zero... I don't want to specify the range, but just want any number with 18 digits...
For your requirement, as #duDE mentioned you can't use a NSInteger to save 18 digit number, but there is a solution using NSString.
NSString *eighteenDigitNumberString = [[NSNumber numberWithInt:1 + arc4random_uniform(9)] stringValue];
for (int i = 0; i < 17; i++) {
eighteenDigitNumberString = [eighteenDigitNumberString stringByAppendingString:[[NSNumber numberWithInt:arc4random_uniform(10)] stringValue]];
}
NSLog(#"eighteenDigitNumberString : %#", eighteenDigitNumberString);
There we go, no need to explain everything is straightforward.
EDITED: if you really want a long long value you can do so:
long long eighteenDigitNumberLongLong = [eighteenDigitNumberString longLongValue];
EDITED: To avoid the leading 0 the initial string has been initiated with a non-zero number and the loop is running only 17 times.
As the maximum value of an NSInteger is NSIntegerMax, you cann't use NSInteger for your purpose:
enum {
NSNotFound = NSIntegerMax
};
Prior to OS X v10.5, NSNotFound was defined as 0x7fffffff.
This is 2147483647 (decimal).
If you need "any number" with 18 digits (as #A-Live assumes), you can take NSFloat for example.
A 18 digit integer will require a long long type.
Create two 9 digit random numbers, multiple one by 10^9 and add to the other.
const u_int32_t digits9 = 1000000000;
u_int32_t ms = arc4random_uniform(digits9);
u_int32_t ls = arc4random_uniform(digits9);
unsigned long long random18 = ((unsigned long long)ms * digits9) + ls;
NSLog(#"Example random18: %018llu", random18);
Output:
Example random18: 501895974656079554
If the number must have a leading non zero digit:
const u_int32_t digits81 = 100000000;
const u_int32_t digits89 = 900000000;
const u_int32_t digits9 = 1000000000;
u_int32_t ms = arc4random_uniform(digits89) + digits81;
u_int32_t ls = arc4random_uniform(digits9);
unsigned long long random18 = ((unsigned long long)ms * digits9) + ls;
If you need strictly 18 digits it would be better to use this code:
NSString *stringNumber = [NSString string];
for (int i = 0; i < 18; i++) {
if (i == 0) {
stringNumber = [stringNumber stringByAppendingString:[NSString stringWithFormat:#"%#", #(arc4random_uniform(9) + 1)]];
} else {
stringNumber = [stringNumber stringByAppendingString:[NSString stringWithFormat:#"%#", #(arc4random_uniform(10))]];
}
}
long long value = stringNumber.longLongValue;
You need the first condition because with the possibility of 0.1 you may receive 0 as the first digit, then your 18-digit integer would become 17-digit, with 0.01 possibility - 16-digit integer etc.
You're getting into unsigned long long territory...
#define ARC4RANDOM_MAX 0x100000000
float val = ((double)arc4random() / ARC4RANDOM_MAX);
unsigned long long numberToAdd = val * (900000000000000000-1);
unsigned long long numberFinal = 100000000000000000 + numberToAdd;
NSLog( #"value = %llu", numberFinal);
When I calculate with numbers in my App, there are no decimals in my output!
this is the code I am using:
int Vijfhonderdmeter = [_VijfHonderd.text intValue];
int vijftienhonderdmeter = [_vijftienhonderd.text intValue];
int vijfdeeldrie = vijftienhonderdmeter / 3;
int puntentotaal = Vijfhonderdmeter + vijfdeeldrie;
_PuntenTotaal.text = [NSString stringWithFormat:#"%d",puntentotaal];
The real output should be 87,18667, but my _Puntentotaal label only shows 87.
Has anyone got a solution?
By the way:
(_VijfHonderd.text intValue = 44.05).
(_vijftienhonderd.text intValue = 129.41).
Thank you for your time and help :)
3 problems:
1) You are using integer division with integer value. If you want floating point numbers you need to floating point values and do floating point division:
double Vijfhonderdmeter = [_VijfHonderd.text doubleValue];
double vijftienhonderdmeter = [_vijftienhonderd.text doubleValue];
double vijfdeeldrie = vijftienhonderdmeter / 3.0;
2) You are formatting the result using %d. Use %f with the double values.
3) Use NSNumberFormatter, not stringWithFormat: to format the numbers. This will ensure they look correct for all users based on their locale.
Your code will always return a non decimal value as you are operating with int datatype.
int Vijfhonderdmeter = [_VijfHonderd.text intValue]; here you are getting a non decimal value & later also you are operating it with other non-decimal values. To get decimal values, you should use `double` or `float` datatype.
For now,try this:
float Vijfhonderdmeter = [_VijfHonderd.text floatValue];
float vijftienhonderdmeter = [_vijftienhonderd.text floatValue];
float vijfdeeldrie = vijftienhonderdmeter / 3;
float puntentotaal = Vijfhonderdmeter + vijfdeeldrie;
_PuntenTotaal.text = [NSString stringWithFormat:#"%f",puntentotaal];
Refer this link for more knowledge about premitive datatypes in objective-C
I am trying to calculate a distance based upon the scale my users draws in.
My user can snap to other points in the game by drawing a line on a grid.
The grid is a numerical value that they can select 8, 16, 24, or 32.
The scale can be changed by the user selecting a whole number (1-10) and fraction number (0, 0.5, 0.75, or 0.125).
The user can select between showing the distance in Metric or Empirical units.
I am having difficulty spitting out the distance once the scale is changed.
Can anyone tell me where I have gone wrong in my math?
- (double) distanceFormula : (float) x1 : (float) y1 : (float) x2 : (float) y2 {
// 1 meter * 3.280839895 feet => feet
// 1 foot * 1 meter/3.280839895 feet => meter
/* Use Pythagora's theorem to calculate distance */
double dx = (x2-x1);
double dy = (y2-y1);
double dist = sqrt(dx*dx + dy*dy);
NSLog(#"Raw Distance %f", dist) ;
return dist;
}
- (NSString *) returnDistanceAsString : (double) distance {
NSString * string;
double d = distance / [self returnGridSize];
double scale = [self returnScaleWhole] + [self returnScaleSub];
if ([self returnUseMetric]) {
//METRIC
int tempCentim = (d * kCMConst) / 2;
if (tempCentim < 1) {
string = [NSString stringWithFormat:#"%d mm", tempCentim];
} else if (tempCentim > 1) {
string = [NSString stringWithFormat:#"%d mm", tempCentim];
} else if (tempCentim > 100) {
//eventually going to add cm mm
}
} else {
//EMPERICAL
int RL = d * scale;
int feet = RL / 12.0;
int inches = (int)RL % 12;
string = [NSString stringWithFormat:#"%i' %i\"", feet, inches];
}
return string;
}
The main questionable thing I noticed is that, in metric, you're neglecting to multiply by scale, but you are multiplying by kCMConst/2. You don't explain what kCMConst is, but it seems unlikely to be a value that needs to be divided by 2.
I have an NSArray of 100 numbers. I would like to create an NSArray of 5 numbers. The first number in the second array is the average of the first 20 numbers in the first array. The second number is the average of the second set of 20 numbers in the first array. And so on.
I'm curious to hear people's ideas for an efficient algorithm.
One idea I had was to do a for-loop on each set of 20 numbers, creating a temp NSArray of 20 numbers. Then perform a KVO average operation and add to the final NSArray.
Note: I always award THE answer to someone, and I'm not shy to up vote your answers. I encourage many answers. Thanks!
Just add the values in each 20 number section, divide by 20 and put in the appropriate output array location. It is one pass through the array, Big O(n), what more could you ask for? The time to compute this is minuscule.
The following is simple and efficient:
NSArray *numbers = ... // array of 100 numbers
NSMutableArray *averages = [NSMutableArray array];
for (int = 0; i < 5; i++) {
float total = 0.0;
int base = i * 20;
for (int j = 0; j < 20; j++) {
float num = [numbers[base + j] floatValue];
total += num;
}
float avg = total / 20.0f;
[averages addObject:#(avg)];
}
NSLog(#"Averages = %#", averages);
you could try something like this...
NSArray *_array = // with the 100 numbers... (I used NSNumber object for each number)
NSMutableArray *_averages = [NSMutableArray array];
for (int i = 0; i < 5; i++) [_averages addObject:#([[[_array subarrayWithRange:NSMakeRange(i * 20, 20)] valueForKeyPath:#"#avg.floatValue"] floatValue])];
the _averages will contain 5 values with the averages of the five different sections of the 100 numbers.
UPDATED:
this part is just for eyes with extra curiosity.
if you tries to avoid the NSObjects and the double for loops, you could achieve a really fast algorithm, and of course when you go lower levels, you can improve the current speed as well, the question is: does it really need?
NSInteger _segments = 1000; // it means 20.000 numbers;
Float64 _numbers[(_segments * 20)]; // fill this array as you'd like.
Float64 _averages[_segments];
for (int i = 0; i < _segments; i++) {
NSInteger _offset = (_segments<<4)+4;
_averages[i] = (_numbers[_offset] + _numbers[_offset+1] + _numbers[_offset+2] + _numbers[_offset+3] + _numbers[_offset+4] + _numbers[_offset+5] + _numbers[_offset+6] + _numbers[_offset+7] + _numbers[_offset+8] + _numbers[_offset+9] + _numbers[_offset+10] + _numbers[_offset+11] + _numbers[_offset+12] + _numbers[_offset+13] + _numbers[_offset+14] + _numbers[_offset+15] + _numbers[_offset+16] + _numbers[_offset+17] + _numbers[_offset+18] + _numbers[_offset+19]) / 20.f;
}
it is 10 times faster than the solution with double for loops and NSObject classes.
(un)fortunately, it is not even the ugliest solution, but there is no question it is fast as hell, I won't recommend it except the speed really matter because that kind of solutions can provide really good efficiency.
I have a UIPickerView that I am populating with an array. It has two columns and i need the first column to go from 50-500. the second column to go from 0.01 to 1. The point is for the user to pick their weight. For doing the 50-500 I have this,
-(void)populateWeightPickerArray{
weightPickerArray = [[NSMutableArray alloc] init];
for (int weight = 50; weight <=500; weight++) {
NSString *weightString = [NSString stringWithFormat:#"%d%",weight];
[weightPickerArray addObject:weightString];
}
}
I tried doing that with the decimal, however when i use ++ it goes up by whole number and I end up getting 1.01, 2.01, 3.01 etc. here is what I have for code.
-(void)populateWeightPickerArray2{
weightPickerArray2 = [[NSMutableArray alloc] init];
for (float weightDecimal = .01; weightDecimal <= 10; weightDecimal++) {
NSString *weightDecimalString = [NSString stringWithFormat:#"%0.2f",weightDecimal];
[weightPickerArray2 addObject:weightDecimalString];
}
}
(I know i said I only needed it to go to 1 not 10, but i put 10 because at first it was only displaying 1.01, so i put 10 to test the output until I can get it right.)
So i need to somehow increment it to make it go from .01 to 1 (0.02, 0.03,0.04 etc). Anyone know how to do this?
for (float weightDecimal = .01; weightDecimal <= 10; weightDecimal = weightDecimal + 0.01)
or more succinctly:
for (float weightDecimal = .01; weightDecimal <= 10; weightDecimal += 0.01)
In Objective C: x += y is a shorthand for x = x + y.
Although you often see for loops with something++ or something-- you can use any expression.