AOC 2024, Day 1: Missing abs() for integers
Let’s solve Advent of Code 2024 Day 1 and investigate about the missing function to calculate absolute for integers!
The below is a guest post by the amazing Shraddha Agrawal.
It’s December, which means its Advent of Code time! A friend introduced me to this challenge a few years back but I generally wouldn’t make it past day 2. Last year, Matt Boyle started a private leaderboard for the same in the Golang Insiders community and I ended by earning all 50 stars. What great fun it was solving all the challenges with the community.
Advent of Code is a challenge that started 10 years ago by Eric Wastl. Throughout the month of December will Christmas 2 new problems are unlocked. The problems vary in levels of difficulty and helps you learn about various diffirent alorithms, data structures and othher programming concepts.
Its 2024 and its time for a new set of problems to solve!
Solving Day 1
This year, unlike the last, the challenge started off pretty easy. The main things needed for solving part 1 were reading file contents, converting string to integers, sorting an int slice, calculating the difference and finding absolute value of integers (…hmm). Part 2 entailed counting reoccurrence of integers in a slice. Pretty standard stuff, right? Right. You can find my solution for day one here.
Why does Go not have abs()
function for integers?
While solving the problem, I was once again struck with the same old question, why does Go not have a function to calculate absolute value for integers? I did a lil digging and other’s have been wondering about this too. See this, this, this and someone took it a step further to implement a really optimised version too!
The function itself is pretty trivial to implement, only a couple lines of code.
func abs(num int) int {
if num < 0 {
return num * -1
}
return num
}
If its this easy, why not have a standard library function for the same?
From Go’s FAQs:
New additions to the standard library are rare and the bar for inclusion is high. Code included in the standard library bears a large ongoing maintenance cost (often borne by those other than the original author), is subject to the Go 1 compatibility promise (blocking fixes to any flaws in the API), and is subject to the Go release schedule, preventing bug fixes from being available to users quickly.
While it is a trivial function to write, adding this function can cascade into an avalanche of requests to add other trivial functions for various data types which would end up increasing the work for language maintainers.
But Go does have a function for calculating the absolute for float64
, what gives?
Lets look at abs()
function for float64
Lets look at the implementation of abs()
(ref: https://github.com/golang/go/blob/master/src/math/abs.go):
func Abs(x float64) float64 {
return Float64frombits(Float64bits(x) &^ (1 << 63))
}
Well, this doesn’t look like a trivial implementation. Let’s unpack this!
Go uses IEEE 754 standard to store float64
. According to the standard, the most significant bit (MSB) represents the sign bit, 0 signifies a positive number and 1 signifies a negative number.
In the above function, Float64bits
converts the float64
input to its uint64
representation as per the IEEE 754 standard.
1<<63
uses left shift operator <<
to shift the bits 63 times such that the result is a uint64
with only the MSB set to 1 followed by 63 zeroes.
&^
is the bit clear operator. It only preserves the bits in the first argument which are unset in the second argument. All the rest of the bits in the first argument are unset, ie set to zero. Truth table for the same:
X | Y | X &^ Y |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 1 |
1 | 1 | 0 |
So, Float64bits(x) &^ 1<<63
would clear the first bit from the uint64
representation of the input, as 1<<63
yields an uint64
without only the MSB set. Clearing MSB in input means we have changed the sign of the float64
number to positive as per IEEE 754 format.
Now, all thats left is to change the uint64
representation of the input back to float64
which is done by Float64frombits
. This implementation is able to handle the special cases for +/- Inf
and NaN
. The complexity of the implementation explains why this warrants a spot in the standard library.
Bonus: History of how this func evolved
In 2008, the function was introduced in this commit when it was called fabs()
with a simple implementation.
func
fabs(arg double) double
{
if arg < 0 {
return -arg;
}
return arg;
}
Next major change was when the signed zero was handled, ie -0 should return 0. At this point, the if statements were replaced with a switch case.
func Fabs(x float64) float64 {
switch {
case x < 0:
return -x
case x == 0:
return 0 // return correctly fabs(-0)
}
return x
}
The F was dropped and function was renamed to Abs() in 2011 with this commit.
In 2015, someone reported on the golang-dev Google group that replacing the switch
statements with if-else
statements was faster than the current implementation and the assembly version! This turned out to be due to a “bug” which disabled inlining for switch statements. Apparently, Go used switch statements in a few tests to disable inlining. This was a known bug, but a bug that was discovered from this thread was that assembly code generation could be improved for the function, see more details here.
Keeping the above in mind, the implementation was changed to use if
statements with this commit.
func Abs(x float64) float64 {
// TODO: once golang.org/issue/13095 is fixed, change this to:
// return Float64frombits(Float64bits(x) &^ (1 << 63))
// But for now, this generates better code and can also be inlined:
if x < 0 {
return -x
}
if x == 0 {
return 0 // return correctly abs(-0)
}
return x
}
And finally the patch that fixed the code generation also updated the function to its latest version that we examined earlier.
func Abs(x float64) float64 {
return Float64frombits(Float64bits(x) &^ (1 << 63))
}
And that’s it! I went down a rabbit hole about Go’s existing and missing abs()
functions and ended up learning a couple things about how the math package evolved over time. It was facscinating to learn how the method evolved over time and how you can contribute to the language by finding bugs and reporting it to the maintainers!
If you’re also solving advent of code, I would love to compare notes and checkout your solution. Reach out to me on Twitter, Bluesky or on e-mail at contact[at]shraddhaag[dot]dev.