Ok I think I figured it out. All this time, I got the binning wrong. Apparently, it's centered around the value (not as it's starting point).
So the 0.5 bin will contain values between (0.45, 0.55) if the binwidth is 0.1. I thought it was 0.5 to 0.6, that's why I keep getting half values. Thanks for the help!
In the example given by @user127649, if you change it to:
df <- data.frame(x = runif(100))
df$y <- ifelse(df$x > 0.45 & df$x < 0.55, df$x, NA) <-- adjust the binwidth here. This is where I got the idea that it's centered around the breaks.
ggplot(df, aes(x)) +
geom_histogram(binwidth = 0.1, fill = 4) +
geom_histogram(aes(y), binwidth = 0.1, fill = 2, data = df)