Palindrome Compression Challenge
Write a program that compresses and decompresses ASCII text losslessly. It should be specialized to work well with palindromes, including case-insensitive and punctuation-insensitive palindromes. The best compression with the smallest source wins.
Scoring
total_bytes_saved / sqrt(program_size) - Highest score wins
total_bytes_saved is how many bytes smaller the compressed strings are than the originals, total across the test cases below. program_size is the size in bytes of the source code of both the compression and decompression programs. Code shared between the two need only be counted once.
For instance, if there are 10 test cases and a 100 byte program saved 5 bytes on 7 test cases, 10 each on 2 of them, but the last test case was 2 bytes longer, the solution would score 5.3. ((7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)
Test Cases

tacocat
toohottohoot
todderasesareddot
amanaplanacanalpanama
wasitacaroracatisaw?
Bob
IManAmRegalAGermanAmI
DogeeseseeGod
A Santa at NASA
Go hang a salami! I'm a lasagna hog.

Rules

Standard loopholes apply.
The compression must work on all printable ASCII (bytes 32-126, inclusive) text strings, not just palindromes. It doesn't actually have to save space for any inputs, however.
Output can be any sequence of bytes or characters, regardless of its implementation or internal representation (strings, lists, and arrays are all fair game, for instance). If encoding to UTF-8, count bytes, not characters. Wide strings (e.g. UTF-16 or UTF-32) are not allowed unless the only codepoints possibly used are between 0 and 255.
Compression/Decompression builtins are not allowed.

For the sake of our own enjoyment, post the compressed strings with your source code.
UPDATE 1: Scoring changed from total_bytes_saved / program_size to total_bytes_saved / sqrt(program_size) in order to give more weight to better compression and less weight to aggressive golfing. Adjust your scores accordingly.
UPDATE 2: fixed wasitacaroraratisaw? to be wasitacaroracatisaw?
JavaScript (ES6), 3.143 (81 bytes saved, 664 byte program)

R='replace',S=String.fromCharCode,T=c=&gt;c.charCodeAt(),U='toUpperCase',V='0000000',W=(a,b,c=2)=&gt;a.toString(c).slice(b),X=x=&gt;'0b'+x,Y=a=&gt;[...a].reverse().join``,Z=/[^]/g
C=s=&gt;S(...((Y(q=s[U]()[R](/[^A-Z]/g,m=''))==q?(q=q.slice(0,p=-~q.length/2),p%1&amp;&amp;10):11)+q[R](Z,x=&gt;W(T(x),2))+111+s[R](Z,c=&gt;/[a-z]/.test(c)?W("00",m,m=1):m+(/[A-Z]/.test(c,m='')?"01":W(c&lt;'!'?2:T(c)+384)))+V).match(/(?!0+$).{8}/g).map(X))
D=s=&gt;{s=s[R](Z,c=&gt;W(256+T(c),1))+V;M=r=&gt;(s=s[R](p=s.match(`^${r}|`)[0],''),p);for([,a]=M`1.|0`,t=u=i='';!M`111`;)t+=W(X(M`.{5}`)-~8,0,36);for(t+=W(Y(t),a?a/0:1);p;)u+=M`0(?=00)|00?1`?(c=t[i++])?+p[1]?c[U]():c:'':M`10`?' ':M`11`&amp;&amp;S(X(M`.{7}`));return u+W(t,i)}


Now that I'm fairly satisfied with this program (and the scoring system), I'll write a bit of an explanation.

The basic idea is to compress the input into a string of bits, then compress each set of 8 bits into a byte. For the purposes of explanation, I'll just manipulate the bit string.

The bit string can be separated into several sections:

input  -&gt; Taco Cat.
output -&gt; 0101000000100011011111110100001100100011101011100000000

0      | 10100 00001 00011 01111 111 | 01 00001 10 01 0001 110101110
header | letter data                 | styling data


The header is a very simple mapping:

0  -&gt; odd-length palindrome
10 -&gt; even-length palindrome
11 -&gt; non-palindrome


Letter data is also fairly simple. First, all non-letters are extracted from the string, and all letters are converted to uppercase. If the resulting string is a palindrome, the reversed half is stripped. Then this mapping is applied:

A -&gt; 00001
B -&gt; 00010
C -&gt; 00011
D -&gt; 00100
...
Z -&gt; 11010


This section is terminated with 111. After that comes the styling data, which stores upper/lower-case data and non-letters. This works like so:

01 -&gt; next letter as uppercase
0...01 (n 0s) -&gt; next (n-1) letters as lowercase
10 -&gt; space
11xxxxxxx -&gt; character with code point 0bxxxxxxx


So going through the example shown above, we have

header: 0 -&gt; palindrome
letter data: 10100 00001 00011 01111 111 -&gt; taco
styling data:
  01        -&gt; T
  00001     -&gt; aco
  10        -&gt; &lt;space&gt;
  01        -&gt; C
  0001      -&gt; at
  110101110 -&gt; .


When the end of the bit string is reached, all remaining characters from the letter data are appended to the result. This saves us from having to do one last 000...001 and allows us to truncate these bits from the string.

Going through the test cases:

tacocat -&gt; 3 bytes (-4)
    24 bits: 010100000010001101111111
toohottohoot -&gt; 5 bytes (-7)
    35 bits: 10101000111101111010000111110100111
todderasesareddot -&gt; 7 bytes (-10)
    49 bits: 0101000111100100001000010110010000011001100101111
amanaplanacanalpanama -&gt; 8 bytes (-13)
    59 bits: 00000101101000010111000001100000110000001011100000100011111
wasitacaroracatisaw? -&gt; 11 bytes (-9)
    84 bits: 010111000011001101001101000000100011000011001001111111000000000000000000001110111111
Bob -&gt; 2 bytes (-1)
    16 bits: 0000100111111101
IManAmRegalAGermanAmI -&gt; 13 bytes (-8)
    98 bits: 00100101101000010111000001011011001000101001110000101100111010100010100101000001010100000010100101
DogeeseseeGod -&gt; 7 bytes (-6)
    54 bits: 000100011110011100101001011001100101111010000000000101
A Santa at NASA -&gt; 8 bytes (-7)
    63 bits: 100000110011000010111010100000011110110010000011000011001010101
Go hang a salami! I'm a lasagna hog. -&gt; 20 bytes (-16)
   154 bits: 1000111011110100000001011100011100001100110000101100000010110101001111010011000000110001100000000111010000110011101001110011000110000000001100000111010111