How do you get the int and modulo (mod) of division with an NSDecimalNumber
Asked Answered
G

5

9

I am confused by NSDecimalNumber and its "behaviors". I have an NSDecimalNumber that represents a dollar value, say $37.50. I'd like to find out how many times say 5.0 goes into that number and then know what's left over. I can get the straight division and get 7.50 but I want 7 mod 2.50. I could convert to an integer but need to save the "cents" so wondering if there's some tricks in the framework?

Garry answered 24/7, 2009 at 3:20 Comment(0)
T
9

Using Peter Hoseys example, but with iOS code:

NSDecimalNumber *dividend = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithDouble:37.5] decimalValue]];

NSDecimalNumber *divisor = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithDouble:5.0] decimalValue]];

NSDecimalNumber *quotient = [dividend decimalNumberByDividingBy:divisor withBehavior:[NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundDown scale:0 raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:NO]];

NSDecimalNumber *subtractAmount = [quotient decimalNumberByMultiplyingBy:divisor];

NSDecimalNumber *remainder = [dividend decimalNumberBySubtracting:subtractAmount];
Typhogenic answered 29/7, 2011 at 15:27 Comment(1)
You have to set right rounding mode if one of dividend or divisor is negative. NSRoundingMode roundingMode = ((dividend.floatValue < 0) ^ (divisor.floatValue < 0)) ? NSRoundUp : NSRoundDown;Purposeful
R
6
  1. Divide the dividend by the divisor, rounding down. This gets you the quotient.
  2. Multiply the divisor by the quotient.
  3. Subtract that from the dividend. This gets you the remainder.

(37.50 // 5) == 7; 7 * 5 == 35; 37.50 - 35 = 2.50.

(Note: // is Python's operator for integral division. I've borrowed it here. Obviously, you should not actually attempt to use // for division in Objective-C code.)

Roz answered 24/7, 2009 at 3:34 Comment(4)
To round down, use floor() from math.h — it takes a double and returns a double.Selestina
If you're using decimal numbers, try to stay within that type as much as possible. Namely, to round use decimalNumberByRoundingAccordingToBehavior:.Excitant
Better yet, use decimalNumberByDividingBy:withBehavior: to do the division. Then you don't need to round separately.Roz
Note that this doesn't work correctly with negative dividend or negative divisor. I've posted a NSDecimalNumber category below that takes care of those cases.Frulla
F
6

Here's a NSDecimalNumber category which also works with negative numbers and negative divisors:

- (BOOL)isNegative
{
    return (NSOrderedDescending == [[NSDecimalNumber zero] compare:self]);
}

- (NSDecimalNumber *)invertedNumber
{
    NSDecimalNumber *negOne = [NSDecimalNumber decimalNumberWithMantissa:1 exponent:0 isNegative:YES];
    return [self decimalNumberByMultiplyingBy:negOne];
}

- (NSDecimalNumber *)moduloFor:(NSDecimalNumber *)divisor
{
    NSRoundingMode roundingMode = ([self isNegative] ^ [divisor isNegative]) ? NSRoundUp : NSRoundDown;
    NSDecimalNumberHandler *rounding = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:roundingMode
                                                                                              scale:0
                                                                                   raiseOnExactness:NO
                                                                                    raiseOnOverflow:NO
                                                                                   raiseOnUnderflow:NO
                                                                                raiseOnDivideByZero:NO];

    // divide and get the remainder
    NSDecimalNumber *quotient = [self decimalNumberByDividingBy:divisor withBehavior:rounding];
    NSDecimalNumber *subtract = [quotient decimalNumberByMultiplyingBy:divisor];

    NSDecimalNumber *modulo = [self decimalNumberBySubtracting:subtract];
    if ([divisor isNegative]) {
        return [modulo invertedNumber];
    }
    return modulo;
}
Frulla answered 21/2, 2013 at 17:50 Comment(1)
I think your conditional inversion of modulo at the end is unnecessary. modulo already carries the correct sign from the quotient. After removing the inversion and just returning modulo the results match Java's BigDecimal.remainder operator for all four combinations of dividend/divisor positive/negative.Relegate
Z
3

Same solution in Swift 4:

    let dividend = NSDecimalNumber(string: Price)
    let divisor = NSDecimalNumber(decimal: 0.05)
    let quotient = dividend.dividing(by: divisor, withBehavior: NSDecimalNumberHandler(roundingMode: .down, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false))
    let subtractAmount = quotient?.multiplying(by: divisor)
    let remainder = dividend.subtracting(anAmount)
 if remainder != 0
 { ...
Zumstein answered 11/4, 2018 at 7:35 Comment(0)
E
1

You could theoretically do some tricks with the NSDecimalNumber methods decimalNumberByDividingBy:, decimalNumberByMultiplyingBy:, and decimalValue. Divide the numbers, grab the decimal number structure, figure out the remainder from the structure, then create a new decimal number with that remainder and multiply that remainder by the original number.

The easier way to do it, however, would probably be to figure out the maximum precision you want (call it N places after the decimal point), multiply the number by 10eN, then just grab the integer value and do your division and mod on that. You run the risk of losing data, especially with very large numbers, however, so check the biggest number you want and figure out what data type - if any - will work for you. NSNumber does support unsignedLongLongValue.

Excitant answered 24/7, 2009 at 3:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.