Average impact of multiple conversion errors

Imagine four people stand in a line. The first looks at a thermometer, and tells the temperature to the person on their right. This continues down the line, until the last person writes the temperature on a sheet of paper. Unfortunately, due to a miscommunication, every person in the line converts the temperature to their preferred unit, without knowing which it already was.

Let's say the people's preferred units are celsius, fahrenheit, fahrenheit, celsius. The first reads the temperature as 20°C. The second converts to fahrenheit, and determines that it is 68°F. The third, not knowing they have received the temperature in °F already, determines that it is 154.4°F. Finally, it is converted back to °C by the fourth person, who determines that it is 68°C.

You will be given two inputs: a temperature (t), and a number of people (n). Your program (or function) should return the average difference between the original and resulting temperatures, for every possible assortment of n people.

There are two temperature units: °F and °C. To convert between them, you can use $$\F=\frac95C+32\$$ and $$\C=\frac59(F-32)\$$.

You can choose whether your program receives the inputted temperature in °F or °C. The first person should always receive an accurate temperature, converted to their preferred unit only if necessary.

As an example, we'll use an inputted temperature of 41°F, and just 3 people. There are 8 temperature preferences possible:

FFF   41°F  ->  41.0  ->  105.8  ->  222.44  ->  222.44°F
FFC   41°F  ->  41.0  ->  105.8  ->   41.00  ->  105.80°F
FCF   41°F  ->  41.0  ->    5.0  ->   41.00  ->   41.00°F
FCC   41°F  ->  41.0  ->    5.0  ->  -15.00  ->    5.00°F
CFF   41°F  ->   5.0  ->   41.0  ->  105.80  ->  105.80°F
CFC   41°F  ->   5.0  ->   41.0  ->    5.0  ->    41.00°F
CCF   41°F  ->   5.0  ->  -15.0  ->    5.00  ->    5.00°F
CCC   41°F  ->   5.0  ->  -15.0  ->  -26.11  ->  -15.00°F

The average distance from 41°F is 54.88°F, which is the program's output.

I/O:

Temperature should be represented as a float, decimal, or fraction of some sort. The accuracy should be within reasonable bounds; for inputs less than ten people at low temperatures there shouldn't be floating point errors or imprecision noticeable in the first four decimal digits.

You may represent the temperatures inputted and outputted as either °F or °C, but must be consistent (although the input can output can be different units as long as they don't change). Temperatures can be negative. The number of people will never be less than two.

The output is referred to as a temperature above, although it's technically the arithmetic mean of multiple temperatures.

Test Cases:

Input unit is the same as output unit for all test cases.

41°F   2   ->  25.2
41°F   3   ->  54.88
41°F   4   ->  77.236444...
41°F   10  ->  295.4268...
20°C   4   ->  57.21218...
-1°C   2   ->  12.133...
-20°C  2   ->  6.2222...
-40°F  4   ->  0

Other:

This is , shortest answer in bytes per language wins!

• But I like Kelvin temperature :(
– tsh
Mar 31 '21 at 3:50
• How can the average distance be negative in the 6th and 7th test cases? Mar 31 '21 at 4:51
• @tsh You need to use 8 as the denominator instead of 6 in your second formula. Mar 31 '21 at 5:17
• If I'm understanding the task correctly, the result shouldn't depend on the starting and the ending units as long as they are the same. I get (20, 4) -> 57.21, (-1, 2) -> 12.13, (-20, 2) -> 6.22. (Seriously, we have the sandbox for a good reason.) Mar 31 '21 at 5:23
• Fun fact: this kind of mistake happened with an aircraft in 1983 (not temperature, but weight in this case), which caused a Boeing 767 to turn into a glider :-) Mar 31 '21 at 5:48

APL (Dyalog Unicode), 41 bytes

(|40+⎕)+.×(|÷≢)1-1.8*1(-,+)⍨⍣(⎕-2)⊢1-3|⍳4

Try it online!

A full program. Takes the number of people, then the initial temperature. TIO version is wrapped in a function for easier testing. As I pointed out in a comment, the initial and final units do not matter as long as they are equal.

The math

The code exploits the fact that Fahrenheit-Celsius conversion has a fixpoint at -40 (-40°F = -40°C). Then we can add 40 to all temperature readings, and then the whole series of conversion reduces into a (positive or negative) integer power of $$\\frac95\$$.

$$F+40=\frac95(C+40), \quad C+40 = \frac59(F+40)$$

Since every temperature value is offset by the constant value of 40, the difference between two offset temperature values is the same as that of the original. Therefore, it suffices to apply the offset to the initial temperature and ignore it to the end.

Let's call $$\r = \frac95\$$.

Now to the part of counting all conversion paths. Let's say the initial reading is in Fahrenheit. Then:

• Initial conversion: F→F ($$\r^0\$$) or F→C ($$\r^{-1}\$$).
• Final conversion: F→F ($$\r^0\$$) or C→F ($$\r^1\$$).
• All inner conversions: C→F ($$\r^1\$$) or F→C ($$\r^{-1}\$$).

So the task reduces to finding all the individual terms in the expansion of $$\(r^0 + r^{-1})(r^1+r^{-1})^{n-2}(r^1+r^0)\$$. Then plug in the value of $$\r\$$, evaluate the average of differences to 1, and then multiply with the initial temperature (with offset).

The code

(|40+⎕)+.×(|÷≢)1-1.8*1(-,+)⍨⍣(⎕-2)⊢1-3|⍳4

⊢1-3|⍳4  ⍝ A fancy way to create an array of 0 ¯1 1 0
⍝ which represents the powers in (1+r^-1)(r+1)
⍣(⎕-2)   ⍝ Repeat n-2 times...
1(-,+)⍨  ⍝ multiply the given polynomial with (r+r^-1)
⍝ and list all powers
1.8*     ⍝ Plug in r = 1.8 and evaluate the powers
1-       ⍝ Difference of each value from 1
(|÷≢)    ⍝ Average of absolute differences from 1
+.×      ⍝ Product and sum with...
(|40+⎕)  ⍝ Absolute difference between -40 and t
• This is so nice. Mar 31 '21 at 6:27

Python 3, 126 bytes

f=lambda t,n,x=0,i=0,j=0:(f(i and 9*t/5+32or t,n,x or t,i+1,0)+f(5/9*(t-32),n,x or t,i+1,1))/2if i-n else abs(t+(32+.8*t)*j-x)

Try it online!

j is 0 or 1 depending on whether current temperature is in F or C.

i denotes the number of conversions that we have already performed. We perform both of the possible conversions and take the mean of the answer.

x is an extra variable just used to keep track of the original temperature.

• Fails at (-40, 4). Mar 31 '21 at 5:21
• @Bubbler fixed. Mar 31 '21 at 5:30
• I got this method to 104 bytes (2 of those savings requiring := from 3.8), but I guess you could probably do much better with Bubbler's method
– kops
Apr 2 '21 at 10:51

APL (Dyalog Unicode), 55 51 bytes

{(2.8×|40+⍺)+.×(|1-1.8*x-⍨⍳2×x)×2/(⍳x←⍵-1)!⍵-2}÷2*⊢

Try it online!

-4 bytes thanks to @tsh

I finally managed to derive the correct version of tsh's formula, though it turns out to be slightly longer than brute force.

$$(1+r)\sum_{p=-n+1}^{n-2}{\binom{n-2}{\left\lfloor\frac{p+n-1}{2}\right\rfloor} r^p \operatorname{sgn}\left(p+\frac{1}{2}\right)} \div 2^n \\ = (1+r)r^{-n+1}\sum_{p=0}^{2n-3}{\binom{n-2}{\left\lfloor\frac{p}{2}\right\rfloor} r^p \operatorname{sgn}\left(p-n-\frac{1}{2}\right)} \div 2^n \\ = (1+r)\sum_{p=-n+1}^{n-2}{\binom{n-2}{\left\lfloor\frac{p+n-1}{2}\right\rfloor} \left|r^p - 1\right|} \div 2^n \\ \text{ where } r = 1.8$$

In array terms, the first and second formula are equivalent to building a vector containing two copies of each binomial coefficient, negating half of it, and multiplying each with suitable power of $$\r\$$.

The code above uses the third line of the formula.

The process of deriving the formula was essentially spotting two copies of Pascal's triangle in the expansion of polynomial $$\(r^0 + r^{-1})(r^1+r^{-1})^{n-2}(r^1+r^0)\$$, defining an intermediate function ($$\[x;y;z]\$$ denotes if x then y else z)

$$f^+(n, r) = \sum_{p=0}^{n-1}{\binom{n-1}{p} r^{2p-n} [2p-n\ge 0; 1; -1]},$$

defining the sum of differences in terms of $$\f^+\$$ as

$$g(n) = f^+(n-1, r) - f^+(n-1, r^{-1}) + rf^+(n-1, r) - r^{-1}f^+(n-1, r^{-1})$$

and simplifying it over an entire sheet of paper.

• I believe r^p*sgn(...) can be replaced by something like abs(r^(p-n)-1), could this save some bytes?
– tsh
Mar 31 '21 at 23:56
• @tsh Yes, it works with abs(r^p-1). Apr 1 '21 at 0:17

JavaScript (Node.js), 84 bytes

T=>U=>g=(N,p)=>(--N?g(N,~-p)+g(N,p+1|0):Math.abs(U**p-1)*(1+U)*(T+40))/2,C=5/9,F=9/5

Try it online!

Bubbler had worked out the correct formula, so I would like to remove the incorrect (many off-by-one errors) one here. You may read more about math formulas from Bubbler's post. You may find out a solution based on that formula from history of this post.

But calculating binomial coefficient costs too many bytes. So using recursion results a shorter solution here. And the recursion function:

$$U=\begin{cases} 5/9 && \text{Input is ℃} \\ 9/5 && \text{Input is ℉} \end{cases}$$

$$g_U\left(n,p\right) = \begin{cases} \left(g_U\left(n-1,p+1\right)+g_U\left(n-1,p-1\right)\right)/2 & n > 1 \\ \left|U^{p}-1\right| & n = 1 \end{cases}$$

$$f(T,U,N)=\left(1+U\right)\left(T+40\right)\left(g_U\left(N-1,0\right)+g_U\left(N-1,-1\right)\right)/4$$

• @Bubbler I mean my program use some formula looks like it. But I'm not sure if I had written down the formula correctly (and most likely to be incorrect). So, please relay on the program not the formula given here if they work differently.
– tsh
Mar 31 '21 at 7:45

J, 84 bytes

g=.32+*&9r5
[:(+/%#)[|@-[(g inv@]`(g@])@.[/@,~)&>2<@([:1&,^:(1-{.)}:^:{:)"1@#:@i.@^]

Try it online!

Takes input and output in F.

Returns exact output as a rational number.

This hasn't been fully golfed, and it's possible there are large savings to be gained from a different approach.

tldr how

• Generate all binary numbers from 0 to 2^(number of people).
• 1 represents "convert to F" and 0 "convert to C".
• Remove 1 from the end of any binary number that ends in 1 (since an initial F person reads the F thermometer correctly).
• Add 1 to the beginning of any binary number beginning with 0 (since a final C reading must be converted to F).
• Evaluate each resulting binary number right-to-left like a pipeline of conversions.
• One nice thing is that J is able to invert the conversion for us, so after defining the F conversion as g, we get the C for free as g inv.
• Finally take the average of the absolute differences from the starting temp.