NOTE
Mr. Gordon has opened a can of worms… 🪱😬
The
remainder(dividingBy:)method that works with theDoubledata type produces some… unexpected results.Why is this?
Here is the explanation from the Apple Developer documentation.
It is very technical, and not necessarily the easiest to understand.
So, let’s try turning to a large language model – ChatGPT in this case – and see if we can generate some clearer examples.
The interaction with ChatGPT is included below, but here is the original series of prompts and responses).
Mr. Gordon’s query
This page describes how the remainder(dividingBy:) instance method of the Double data type in Swift rounds off values:
https://developer.apple.com/documentation/swift/double/remainder(dividingby:)
It implements remainder operation as defined by the IEEE 754 specification.
Can you please summarize how remainders are calculated, using language that would be easily understood by a high school student that is just starting to learn about programming?
In particular, please include examples that explain why this code:
let remainder = Double(16.5).remainder(dividingBy: 2.5)… results in remainder having a value of -1, whereas this code:
let otherRemainder = Double(13.5).remainder(dividingBy: 2.5)… results in otherRemainder having a value of 1.
ChatGPT’s response
Sure! Let’s break it down in a way that’s easy to follow.
In Swift, the remainder(dividingBy:) method works a little differently from the regular modulo operation you may have seen before. This method follows the IEEE 754 specification, which means the remainder it returns isn’t just what’s left after dividing two numbers—it also considers which result is closer to 0 on the number line. This is why sometimes the remainder can be negative. Let’s walk through the examples you mentioned.
How remainder(dividingBy:) Works
When you divide two numbers using remainder(dividingBy:), Swift:
- Divides the first number (called the dividend) by the second number (called the divisor).
- Rounds the quotient (the result of the division) to the nearest whole number.
- Uses this rounded quotient to figure out what part is left over, or the remainder.
If the quotient is exactly halfway between two numbers, it rounds to the nearest even number (this is called rounding to even or bankers’ rounding).
Example 1
let remainder = Double(16.5).remainder(dividingBy: 2.5)- Dividend: 16.5
- Divisor: 2.5
- Exact Quotient: 16.5 ÷ 2.5 = 6.6
(The quotient is between 6 and 7.)
Since 6.6 is closer to 7, we round the quotient to 7.
- Rebuilding the result:
But the original number was 16.5, so the remainder is:
Answer: The remainder is -1.
Example 2
let otherRemainder = Double(13.5).remainder(dividingBy: 2.5)- Dividend: 13.5
- Divisor: 2.5
- Exact Quotient: 13.5 ÷ 2.5 = 5.4
(The quotient is between 5 and 6.)
Since 5.4 is closer to 5, we round the quotient to 5.
- Rebuilding the result:
The original number was 13.5, so the remainder is:
Answer: The remainder is 1.
Why Are the Remainders Different?
The key reason is that remainder(dividingBy:) rounds the quotient to the nearest whole number, and depending on whether that number is higher or lower, the remainder can flip between positive or negative.
- In the first example, rounding gave us 7, which resulted in a remainder of -1.
- In the second example, rounding gave us 5, which resulted in a remainder of 1.
A Simple Rule of Thumb for remainder(dividingBy:)
- It tries to find the remainder that’s closest to 0 on the number line.
- If the quotient is exactly halfway between two numbers, it rounds to the nearest even number, which can affect whether the remainder is positive or negative.
I hope this makes things clearer! Let me know if you have any questions or if anything is still confusing.
Mr. Gordon’s followup question
OK, that’s easy enough to understand.
Can you elaborate on why the IEEE 754 defines the remainder operation in this way?
How is that approach to describing the remainder more useful than simply saying that, for example, the remainder when 18.0 divided by 5.0 is 3.0?
ChatGPT’s response
Great question! Let’s dive into why the IEEE 754 specification defines the remainder operation the way it does and why it’s useful in certain contexts, especially for computers and math-heavy calculations.
The Reason for the IEEE 754 Approach
IEEE 754 is a standard for how computers handle floating-point numbers, which are numbers with decimals. The key goals of this standard are to ensure:
- Precision: So that small rounding errors don’t add up over time.
- Consistency: So that different computers and software get the same results when doing math.
The IEEE 754 remainder operation is designed with these goals in mind. Here’s why it does things a little differently than the simpler modulo operation (like you mentioned, where 18.0 ÷ 5.0 gives a remainder of 3.0):
-
Round to the Nearest Quotient for Accuracy: The IEEE 754 remainder operation rounds the quotient to the nearest integer to give a remainder that is as close to 0 as possible. This approach helps reduce errors, especially in scientific and engineering applications where very small differences can have a big impact.
For example:
- If you divide 16.5 by 2.5, getting a remainder of -1 is actually a smaller difference (closer to 0) than 1.5, which is what you’d get from the standard modulo operation.
This is more accurate when working with numbers that represent real-world measurements, where precision is key.
-
Positive or Negative Remainders: The remainder can be positive or negative because the idea is to find the shortest distance to zero. This is different from the modulo operation, where the remainder is always positive.
Imagine a number line where you’re looking for the closest “multiple” of your divisor:
- For 16.5 ÷ 2.5, the closest multiple of 2.5 is 17.5, so the remainder is -1.
- For 13.5 ÷ 2.5, the closest multiple is 12.5, so the remainder is 1.
This allows the operation to “flip directions” when needed, making it more suitable for circular calculations, like angles or trigonometry (e.g., thinking about wrapping around a circle where sometimes you need a negative remainder to go backward).
Why Not Just Use Simple Modulo?
The simple modulo operation works well when you’re just interested in splitting things evenly and don’t care about precision beyond whole numbers. For example, if you’re dividing pizza slices or handling file sizes.
However, the IEEE 754 remainder approach is useful in situations where:
-
Precision Matters: In scientific computing, finance, and engineering, small differences in rounding can lead to significant errors over many calculations. The IEEE method minimizes those errors.
-
Symmetry in Math: When doing math that involves symmetry, like trigonometric functions (sine, cosine), physics simulations, or graphics programming, it’s important to have remainders that are evenly distributed and as close to zero as possible, even if that means having negative remainders.
-
Circular Calculations: If you’re working with angles (think of rotating objects in a video game or doing GPS calculations), the IEEE remainder makes sense because the shortest path back to a reference point could be positive or negative, depending on which way you’re rotating.
Example: Angle Calculations
Imagine you’re working with angles. Suppose you want to find out how far a given angle is from a full 360° rotation:
- Using modulo, if you divide 370° by 360°, you get a remainder of 10°, which is correct for a positive angle.
- But with IEEE remainder, if your angle was something like -370°, it would give you a remainder of -10°, meaning you’re 10° away but in the negative direction (counter-clockwise). This might be the direction you need to rotate in some cases.
This negative remainder can be more useful in calculations involving circular motion, physics simulations, or any scenario where direction matters.
In Short:
- IEEE 754 remainder is more precise because it minimizes rounding errors.
- It provides a remainder that’s closest to zero on the number line, which can be positive or negative.
- This is more useful in certain math-heavy fields, like physics, graphics, or finance, where precision and symmetry are important, and rounding errors can add up over time.