I’ve just started a lot of new things in my life; programming Scala (and Python), data engineering and a new client.
Luckily the things we have built until now is algorithmically pretty simple and … what was that? Calculate the whatnow temperature?! The wetbulb temperature? Wash your mouth with soap, young man! What on earth is that?!
And you want me to write this in Scala?! This is horrible.
In this blog post I’ll walk you through my thinking and application of experience to write one of the most complicated calculations I’ve ever put into a program, in a programming language that I’ve never seen 2 months ago.
It actually took me all of 1 hour to get this to work  and still be confident that it works.
Wetbulb temperature
Part of this clients request is to supply some weather data for a certain location and time. And in doing so they also wanted us to extend the weather data with the addition of the wetbulb temperature.
I don’t know what that is, nor do I particulary care, but having a clue what it is is always a good idea. Google and Wikipedia to our help:
The wetbulb temperature (WBT) is the temperature read by a thermometer covered in watersoaked (water at ambient temperature) cloth (a wetbulb thermometer) over which air is passed
Haha  where do they even come up with these things. Ok  the next sentence is also interesting:
At 100% relative humidity, the wetbulb temperature is equal to the air temperature (drybulb temperature); at lower humidity the wetbulb temperature is lower than drybulb temperature because of evaporative cooling.
Sounds like a test case to me.
How to calculate it
I then search for “calculate wetbulb temperature” and was presented with a nice calculator at the OmniCalculator.
It further helped me to understand that I only needed two inputs; temperature and relative humitidy in percentage. I had both of them in the data already.
At OmniCalculator I could also try to calculate some of the things. I used my current temperature, just to see:
Temp: 2.53°C
Humidity: 89%
Wetbulb temperature: 1.479 °C
Eeeh  ok. What about something weird from my time in Indonesia:
Temp: 38°C
Humidity: 85%
Wetbulb temperature: 35.66 °C
I still don’t get anything, but ok, this is a start.
Look it even has a How to calculate wetbulb temperature section let’s loo…aaaaaaaaah Mother of GOD!
I have no formal math training and this scared the crap out of me. What even is arctan
? Raised to the power of 3/2! Is this is a joke?
Ok  now I was really worried, but I pressed on.
Surely I cannot be first in this…
Previous code?
I actually had some previous code to start from, but it was implemented using Python (that I also don’t really know) and Pandas (that makes my knowledge of Python evaporate like a wet cloth… no, wait … that makes it harder still to understand).
Someone else maybe had written this before? Yes. Yes, they had. I found an implementation in JavaScript that I could look at:
//Calculate Wet Bulb
var WB = T * Math.atan(.151977 * Math.sqrt(RH+8.313659) )+Math.atan(T+RH)  Math.atan(RH1.676331)+ Math.pow(.00391838*RH,1.5)*Math.atan(.023101*RH) 4.686035;
Still, very weird. But checking the code helped me a bit:
 They are using the same constants
0.00391838
for example. So it lines up with the OmniCalculator.  Secondly, they are using
Math.atan
in place ofarctan
and after a quick check I understood thatarctan
is calledatan
in Scala
Implementation  my strategy
Since I don’t know the math and am very new to Scala I realized that I needed to create tests to ensure that I got the algorithm correct. I now could check that using the OmniCaclulator as my source of truth.
In Google Sheets I generated a bunch (20+) test cases by creating one column for temp, one for humidity. I then randomized some data within reason:
Temp: =RANDBETWEEN(20, 40)
Humidity: =RANDBETWEEN(5, 99)
See Summary for why I choose those numbers for humidity.
(It might not make sense to caclulate the wetbulb temp for that cold temperature, but I’m just checking an algorith so I don’t care)
I then ran each of these pair of values through the OmniCalculator and added a third column with the result.
I now had a bunch of test cases.
Implementing in Scala
First write a test
I knew some Scala and decided to write a test, and for the first try I hard coded the initial values I tried before:
class WetBulbTempTests extends AnyFunSuite {
// other code here
test("WetBulb(Temp=2.53, Humidity=89%) == 1.479") {
calculateWetBulbTemperature(2.53, 89) should be(1.479)
}
}
and then hardcode the implementation, just to get references and stuff in place:
object WetBulbTempCalculator {
// other code here
def calculateWetBulbTemperature(temperature: Double, humidity: Double): Double = {
1.479
}
}
After some fiddling with refrence (not really, since IntelliJ is great in helping me) that, unsurprisingly worked.
Then add many tests
That was Red and Green. Let’s add some more tests. In fact, I rewrote that test so that it looked like this:
test("WetBulb(Temp=2.53, Humidity=89%) == 1.479") {
calculateWetBulbTemperature(2.53, 89) should be(1.479)
}
Now I had one test, but I want many to ensure that I’m doing this correctly. I used my favorite code generator, Google Sheet. Remember I had columns for Temp
, Humidity
and Results
.

I added a column before the
Temp
column where I added'calculateWetBulbTemperature(
. The
'
is important to make Google Sheet understand that this is a text.
 The
 I added a new column between the
Temp
andHumidity
columns with the this content',
 After the
Humidity
column I added a new column with this content') should be(
 Finally after the
Results
column I added a new column with :')
 Finally I dragged these values across all rows I had
Temp
,Humidity
andResults
for.
Now I copied them into IntelliJ and BOM loads of test cases:
calculateWetBulbTemperature( 38 , 5 ) should be( 14.828 )
calculateWetBulbTemperature( 18 , 92 ) should be( 17.04 )
calculateWetBulbTemperature( 9 , 84 ) should be( 10.117 )
calculateWetBulbTemperature( 28 , 65 ) should be( 22.974 )
calculateWetBulbTemperature( 6 , 53 ) should be( 8.58 )
calculateWetBulbTemperature( 1 , 63 ) should be( 3.697 )
calculateWetBulbTemperature( 12 , 70 ) should be( 13.532 )
calculateWetBulbTemperature( 23 , 98 ) should be( 22.77 )
calculateWetBulbTemperature( 5 , 49 ) should be( 7.825 )
calculateWetBulbTemperature( 23 , 31 ) should be( 13.26 )
calculateWetBulbTemperature( 42 , 53 ) should be( 33.284 )
calculateWetBulbTemperature( 44 , 27 ) should be( 27.966 )
calculateWetBulbTemperature( 5 , 58 ) should be( 1.381 )
calculateWetBulbTemperature( 7 , 60 ) should be( 9.254 )
calculateWetBulbTemperature( 11 , 16 ) should be( 11.8 )
calculateWetBulbTemperature( 4 , 96 ) should be( 4.452 )
calculateWetBulbTemperature( 28 , 81 ) should be( 25.355 )
calculateWetBulbTemperature( 10 , 48 ) should be( 4.918 )
calculateWetBulbTemperature( 22 , 92 ) should be( 20.997 )
calculateWetBulbTemperature( 27 , 92 ) should be( 25.945 )
(The spaces was automatically fixed by IntelliJ). Yes, I put all of those checks in one test case. Sue me!
If I could get all of those test cases to work I was now pretty confident that my code would actually be correct, according to the OmniCalculator.
Draw the rest of the Owl… write the code
The next step is huge, since I didn’t see a way to do this in steps.
But after copying the JavaScript code straight into the calculateWetBulbTemperature
I actaully looked awful. I commented it out and then carefully structured the code into lines, putting each part of the formula on it’s own line.
I then checked against the formula from OmniCalculator and before long it looked like this:
def calculateWetBulbTemperature(temperature: Double, humidity: Double): Double = {
temperature *
math.atan(0.151977 * math.pow((humidity + 8.313659), 0.5)) +
math.atan(temperature + humidity) 
math.atan(humidity  1.676331) +
(0.00391838 * math.pow(humidity, 1.5)) *
math.atan(0.023101 * humidity) 
4.686035
}
The JavaScript code used the Math.sqrt
instead of Math.pow(X, 0,5)
that I have above. I decided to follow the formula as close I could.
Optimize for readablility
Let’s pause here and just appreciate how much you learn about the code by just writing sensible lines. Compare this code to the first version I got from pasting the code into the editor:
def calculateWetBulbTemperature(temperature: Double, humidity: Double): Double = {
temperature * math.atan(0.151977 * math.pow((humidity + 8.313659), 0.5)) + math.atan(temperature + humidity)  math.atan(humidity  1.676331) + (0.00391838 * math.pow(humidity, 1.5)) * math.atan(0.023101 * humidity)  4.686035
}
It’s the same code, just less readable. I always optimize for readability for the next developer (Hey it’s probably me) first and if hardpressed other values (like performance) later.
In this case, the act of formatting the code into it’s constitutent parts also helped me understand it.
Running the tests
I now ran the first test and belive it or not, but it worked. Running all the other tests … did not. It failed on 17.04
. Now I was in real trouble, because fixing this would be impossible. For me.
Until I saw that the error message said that 17.04
is not equal to 17.03832515115
. A roundning error. I can fix that!
So I updated my implementation to round to 3 decimals, and changed the test data to use roundning to 3 decimals:
def calculateWetBulbTemperature(temperature: Double, humidity: Double): Double = {
val wetBulbTemperature = temperature *
math.atan(0.151977 * math.pow((humidity + 8.313659), 0.5)) +
math.atan(temperature + humidity) 
math.atan(humidity  1.676331) +
(0.00391838 * math.pow(humidity, 1.5)) *
math.atan(0.023101 * humidity) 
4.686035
BigDecimal(wetBulbTemperature).setScale(3, BigDecimal.RoundingMode.HALF_UP).toDouble
}
This part actually ended up taking me about 20 minutes to figure out. Yes, I’m new to this…
Summary
With that I’m pretty confident that my 25, or so, test cases and this stolen implementation would do the trick. If not I can always add more test cases later, but I would be hardpressed to think that it would fail.
There’s cases where OmniSharp doesn’t give an answer (for humidity above 99% and below 5% for example), so trust my alogrithm implementation to handle all other cases too.
I was very proud when I was done, but really I just used test to verify my work and translated and reformatted the code. The code was for the most part stolen.
I hope you found this useful.