From f85a207035dfc544f0fe14c34e81428e469fb62b Mon Sep 17 00:00:00 2001 From: Zachary Levy Date: Tue, 23 Jun 2026 08:59:50 -0700 Subject: [PATCH] Added fahrenheit unit --- quantity/temperature.odin | 125 ++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 12 deletions(-) diff --git a/quantity/temperature.odin b/quantity/temperature.odin index 28d9992..68564b1 100644 --- a/quantity/temperature.odin +++ b/quantity/temperature.odin @@ -17,6 +17,11 @@ kelvins_celsius_offset :: #force_inline proc "contextless" ( return OFFSET } +@(private = "file") +FAHRENHEIT_PER_CELSIUS_DEGREE :: 9.0 / 5.0 +@(private = "file") +CELSIUS_PER_FAHRENHEIT_DEGREE :: 5.0 / 9.0 + // --------------------------------------------------------------------------------------------------------------------- // ----- Types ------------------------ // --------------------------------------------------------------------------------------------------------------------- @@ -39,6 +44,13 @@ kelvins_to_deci_kelvins :: #force_inline proc "contextless" ( return Deci_Kelvins(V){kelvins.v * DECI} } +@(private = "file") +kelvins_to_fahrenheit :: #force_inline proc "contextless" ( + kelvins: Kelvins($V), +) -> Fahrenheit(V) where intrinsics.type_is_numeric(V) { + return celsius_to_fahrenheit(kelvins_to_celsius(kelvins)) +} + //----- Decikelvins ---------------------------------- Deci_Kelvins :: struct($V: typeid) where intrinsics.type_is_numeric(V) { v: V, @@ -70,6 +82,17 @@ celsius_to_deci_celsius :: #force_inline proc "contextless" ( return Deci_Celsius(V){degrees_celsius.v * DECI} } +@(private = "file", fast_math = {.Allow_Contract}) +celsius_to_fahrenheit :: #force_inline proc "contextless" ( + degrees_celsius: Celsius($V), +) -> Fahrenheit(V) where intrinsics.type_is_numeric(V) { + when intrinsics.type_is_float(V) { + return Fahrenheit(V){degrees_celsius.v * FAHRENHEIT_PER_CELSIUS_DEGREE + 32.0} + } else { + return Fahrenheit(V){degrees_celsius.v * 9 / 5 + 32} + } +} + //----- Deci Degrees Celsius ---------------------------------- Deci_Celsius :: struct($V: typeid) where intrinsics.type_is_numeric(V) { v: V, @@ -82,12 +105,36 @@ deci_celsius_to_celsius :: #force_inline proc "contextless" ( return Celsius(V){deci_degrees_celsius.v / DECI} } +//----- Degrees Fahrenheit ---------------------------------- +Fahrenheit :: struct($V: typeid) where intrinsics.type_is_numeric(V) { + v: V, +} + +@(private = "file") +fahrenheit_to_celsius :: #force_inline proc "contextless" ( + degrees_fahrenheit: Fahrenheit($V), +) -> Celsius(V) where intrinsics.type_is_numeric(V) { + when intrinsics.type_is_float(V) { + return Celsius(V){(degrees_fahrenheit.v - 32.0) * CELSIUS_PER_FAHRENHEIT_DEGREE} + } else { + return Celsius(V){(degrees_fahrenheit.v - 32) * 5 / 9} + } +} + +@(private = "file") +fahrenheit_to_kelvins :: #force_inline proc "contextless" ( + degrees_fahrenheit: Fahrenheit($V), +) -> Kelvins(V) where intrinsics.type_is_numeric(V) { + return celsius_to_kelvins(fahrenheit_to_celsius(degrees_fahrenheit)) +} + // --------------------------------------------------------------------------------------------------------------------- // ----- Conversion Overloads ------------------------ // --------------------------------------------------------------------------------------------------------------------- to_kelvins :: proc { deci_kelvins_to_kelvins, celsius_to_kelvins, + fahrenheit_to_kelvins, } to_deci_kelvins :: proc { @@ -97,12 +144,18 @@ to_deci_kelvins :: proc { to_celsius :: proc { kelvins_to_celsius, deci_celsius_to_celsius, + fahrenheit_to_celsius, } to_deci_celsius :: proc { celsius_to_deci_celsius, } +to_fahrenheit :: proc { + celsius_to_fahrenheit, + kelvins_to_fahrenheit, +} + // --------------------------------------------------------------------------------------------------------------------- // ----- Tests ------------------------ // --------------------------------------------------------------------------------------------------------------------- @@ -126,32 +179,80 @@ test_kelvins_to_deci_kelvins :: proc(t: ^testing.T) { @(test) test_deci_kelvins_to_kelvins :: proc(t: ^testing.T) { - deci_kelvins := Deci_Kelvins(int){1000} - kelvins := to_kelvins(deci_kelvins) + deci_kelvins := Deci_Kelvins(int){1000} + kelvins := to_kelvins(deci_kelvins) - testing.expect_value(t, kelvins, Kelvins(int){100}) + testing.expect_value(t, kelvins, Kelvins(int){100}) } @(test) test_celsius_to_kelvins :: proc(t: ^testing.T) { - degrees_celsius := Celsius(f32){0} - kelvins := to_kelvins(degrees_celsius) + degrees_celsius := Celsius(f32){0} + kelvins := to_kelvins(degrees_celsius) - testing.expect_value(t, kelvins, Kelvins(f32){273.15}) + testing.expect_value(t, kelvins, Kelvins(f32){273.15}) } @(test) test_celsius_to_deci_celsius :: proc(t: ^testing.T) { - degrees_celsius := Celsius(int){100} - deci_degrees_celsius := to_deci_celsius(degrees_celsius) + degrees_celsius := Celsius(int){100} + deci_degrees_celsius := to_deci_celsius(degrees_celsius) - testing.expect_value(t, deci_degrees_celsius, Deci_Celsius(int){1000}) + testing.expect_value(t, deci_degrees_celsius, Deci_Celsius(int){1000}) } @(test) test_deci_celsius_to_celsius :: proc(t: ^testing.T) { - deci_degrees_celsius := Deci_Celsius(int){1000} - degrees_celsius := to_celsius(deci_degrees_celsius) + deci_degrees_celsius := Deci_Celsius(int){1000} + degrees_celsius := to_celsius(deci_degrees_celsius) - testing.expect_value(t, degrees_celsius, Celsius(int){100}) + testing.expect_value(t, degrees_celsius, Celsius(int){100}) +} + +@(test) +test_celsius_to_fahrenheit :: proc(t: ^testing.T) { + degrees_celsius := Celsius(int){100} + degrees_fahrenheit := to_fahrenheit(degrees_celsius) + + testing.expect_value(t, degrees_fahrenheit, Fahrenheit(int){212}) +} + +@(test) +test_fahrenheit_to_celsius :: proc(t: ^testing.T) { + degrees_fahrenheit := Fahrenheit(int){212} + degrees_celsius := to_celsius(degrees_fahrenheit) + + testing.expect_value(t, degrees_celsius, Celsius(int){100}) +} + +@(test) +test_kelvins_to_fahrenheit :: proc(t: ^testing.T) { + kelvins := Kelvins(int){373} + degrees_fahrenheit := to_fahrenheit(kelvins) + + testing.expect_value(t, degrees_fahrenheit, Fahrenheit(int){212}) +} + +@(test) +test_fahrenheit_to_kelvins :: proc(t: ^testing.T) { + degrees_fahrenheit := Fahrenheit(int){212} + kelvins := to_kelvins(degrees_fahrenheit) + + testing.expect_value(t, kelvins, Kelvins(int){373}) +} + +@(test) +test_celsius_to_fahrenheit_f64 :: proc(t: ^testing.T) { + // -40 is the point where the Celsius and Fahrenheit scales coincide. It converts exactly in + // f64, so a passing equality here also confirms the 9/5 ratio constant is not lossy. + degrees_fahrenheit := to_fahrenheit(Celsius(f64){-40}) + + testing.expect_value(t, degrees_fahrenheit, Fahrenheit(f64){-40}) +} + +@(test) +test_fahrenheit_to_celsius_f64 :: proc(t: ^testing.T) { + degrees_celsius := to_celsius(Fahrenheit(f64){-40}) + + testing.expect_value(t, degrees_celsius, Celsius(f64){-40}) }