How to Correctly Sum Up Numbers
The article highlights numerical overflow and underflow complexities in programming, detailing how various languages manage these issues and emphasizing the importance of proper handling to ensure accurate results.
Read original articleThe article discusses the complexities of summing numbers in programming, particularly focusing on numerical overflow and underflow issues that can arise in various programming languages and database systems. It begins with a simple example of summing integers and highlights potential pitfalls, such as overflow when the sum exceeds the maximum representable value for a given data type. The article explains how different programming languages handle overflow: C++ treats overflow as undefined behavior for signed integers, while Rust panics in debug mode but wraps results in release mode. Python, on the other hand, uses variable-sized integers to avoid overflow entirely. The article emphasizes the importance of handling these exceptional cases correctly to prevent unexpected results, especially in database systems like CedarDB, which strictly avoid silent overflows. It also discusses strategies for overflow detection, such as using larger intermediate types and leveraging CPU flags for efficient checking. The conclusion reiterates the necessity of proper overflow management in arithmetic operations to ensure accurate results in programming and database queries.
- Numerical overflow can lead to unexpected results in programming and databases.
- Different programming languages have varying methods for handling overflow and underflow.
- CedarDB prioritizes strict overflow handling to avoid silent errors in queries.
- Using larger data types for intermediate results can help prevent overflow.
- Efficient overflow checking functions are essential for maintaining data integrity.
Related
Crafting Formulas: Lambdas All the Way Down
The article details arbitrary-precision arithmetic in the Bruijn programming language, focusing on integers, rationals, and the challenges of real numbers, while discussing efficient representations and computational complexities.
Floating Point Math
Floating point math in computing can cause inaccuracies in decimal calculations due to binary representation limitations. Different programming languages manage this with varying precision, affecting results like 0.1 + 0.2.
An Overview of C++26: Concurrency
C++26 introduces the std::execution framework for better concurrency management, saturation arithmetic to prevent overflow, improved debugging functions, and addresses lock-free data structure challenges with Read-Copy Update and Hazard Pointers.
The big and Small of JavaScript numbers (2017)
JavaScript numbers, as double-precision floating-point values, can lead to unique behaviors. The article discusses safe division, value clamping, and simplifying logic to enhance code reliability and clarity.
Perspectives on Floating Point
The article explains floating point arithmetic's role in approximating real numbers, highlighting round-off errors, the significance of relative error, and the IEEE 754 standard, while noting subtraction's potential for numerical instability.
For example, this is the proposed rust solution (imho the most readable):
fn sum_up_numbers_correctly(numbers: &[i32]) -> Option<i32> {
let mut sum: i32 = 0;
for i in numbers {
sum = sum.checked_add(*i)?;
}
Some(sum)
}
If you try to sum [i32::MAX, i32::MIN, 2] this will return None, while the naive wrapping addition would have returned 1. I'd call None a safe result, it's the algorithm bailing out when it's unsure, and 1 the correct result.The upside of wrapping addition is that you can overflow as often as you want, as long as the result is within the range of the type, the result (of addition and subtraction) is correct. The downside is that you don't know if the result is within range. So you trade between correctly handling a larger range of inputs with wrapping or being more certain of the result with checked overflow.
My preferred solution would have been just upcasting. If we make sum an i64 we know it can't overflow the intermediate if you sum less than 2^32 i32 numbers, and on a 64 bit machine you lose basically nothing by doing that. Keep the checked_add if you are concerned about people summing extremely long lists of numbers
fn sum_up_numbers_correctly(numbers: &[i32]) -> Option<i32> {
let mut sum: i64 = 0;
for i in numbers {
sum = sum.checked_add(*i as i64)?;
}
Some(sum.try_into().ok()?)
}
(the try_into().ok() turns values that overflow i32 into None).When summing i64 you can do the same trick by summing into an i128
You can't memorize your way into software engineering. You can't say, "always wrap-around when an operation overflows" or "always tell the user (panic) when an operation overflows".
This article is a great example. C wraps unsigned integer addition on overflow, but Python upscales to arbitrary integer sizes. The trade-off, of course, is performance. If you're writing a real-time engine (for a rocket or a video game) and you can guarantee integer sizes, then you need to use the fastest algorithm possible. But if you're writing a general-purpose calculator, you might prefer upscaling or at least catching overflows and informing the user.
In almost any other disciple there would be a "correct" way to do it, or at least an accepted "best practice." But for software engineers, it's always, "it depends."
fn sum_up_numbers_correctly(numbers: &[i32]) -> Option<i32> {
let mut sum: i32 = 0;
for i in numbers {
sum = sum.checked_add(*i)?;
}
Some(sum)
}
I expected it to also mention the Kahan summation algorithm (https://en.wikipedia.org/wiki/Kahan_summation_algorithm), which gives more accurate results than this checked_add solution, but it did not. I wonder if that will be in one of the subsequent blog posts. alias tally='awk -F '\''[^-.0-9]'\'' '\''{ for (i = 1; i <= NF; i++) s += $i } END { print s }'\'''
Related
Crafting Formulas: Lambdas All the Way Down
The article details arbitrary-precision arithmetic in the Bruijn programming language, focusing on integers, rationals, and the challenges of real numbers, while discussing efficient representations and computational complexities.
Floating Point Math
Floating point math in computing can cause inaccuracies in decimal calculations due to binary representation limitations. Different programming languages manage this with varying precision, affecting results like 0.1 + 0.2.
An Overview of C++26: Concurrency
C++26 introduces the std::execution framework for better concurrency management, saturation arithmetic to prevent overflow, improved debugging functions, and addresses lock-free data structure challenges with Read-Copy Update and Hazard Pointers.
The big and Small of JavaScript numbers (2017)
JavaScript numbers, as double-precision floating-point values, can lead to unique behaviors. The article discusses safe division, value clamping, and simplifying logic to enhance code reliability and clarity.
Perspectives on Floating Point
The article explains floating point arithmetic's role in approximating real numbers, highlighting round-off errors, the significance of relative error, and the IEEE 754 standard, while noting subtraction's potential for numerical instability.