Author Archive

Using HTML5 Fractions in Exported Excel Tables

HTML5 provides for fractional representation of halves, thirds, fourths, fifths, sixths, no sevenths, and eighths. Excel has a fractional number format. This post is about bringing these concepts together for exporting an Excel table into Wiki or HTML designs. The basic representations are:

Name Hex Dec Result
½ U+000BD 189 ½
⅓ U+02153 8531
¼ U+000BC 188 ¼
⅕ U+02155 8533
⅙ U+02159 8537
⅛ U+0215B 8539
⅔ U+02154 8532
⅖ U+02156 8534
¾ U+000BE 190 ¾
⅗ U+02157 8535
⅜ U+0215C 8540
⅘ U+02158 8536
⅚ U+0215A 8538
⅝ U+0215D 8541
⅞ U+0215E 8542

 
The format for the name is &fracnd; where n is the numerator and d is the denominator. Thus ½ is a half, and ⅞ is seven-eighths. The HTML code representations for these are:

Result Named
Code
Hex
Code
Dec
Code
½ ½ ½ ½
⅓ ⅓ ⅓
¼ ¼ ¼ ¼
⅕ ⅕ ⅕
⅙ ⅙ ⅙
⅛ ⅛ ⅛
⅔ ⅔ ⅔
⅖ ⅖ ⅖
¾ ¾ ¾ ¾
⅗ ⅗ ⅗
⅜ ⅜ ⅜
⅘ ⅘ ⅘
⅚ ⅚ ⅚
⅝ ⅝ ⅝
⅞ ⅞ ⅞

 
In theory (more on “in practice” later) every representation in a row is equivalent. This is our test table to export to Wiki or HTML format:

D E F G H I J K L
1 1/1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/80
2 2/1 2/2 2/3 2/4 2/5 2/6 2/7 2/8 2/8.
3 3/1 3/2 3/3 3/4 3/5 3/6 3/7 3/8 3/8A
4 4/1 4/2 4/3 4/4 4/5 4/6 4/7 4/8 6 4/8
5 5/1 5/2 5/3 5/4 5/5 5/6 5/7 5/8 7 5/8
6 6/1 6/2 6/3 6/4 6/5 6/6 6/7 6/8 8 6/8
7 7/1 7/2 7/3 7/4 7/5 7/6 7/7 7/8 9 7/8
8 8/1 8/2 8/3 8/4 8/5 8/6 8/7 8/8 10 8/8

 
Cell D1: =CHAR(32)&ROW()&”/”&COLUMN()-3, then fill down and right. The right hand column has a few test cases. I used that formulaic construction to keep Excel from doing the divisions. The Excel fractional format is “# ?/?” for single digit denominators. The intermediary space is important. It indicates that a fraction may follow, just as a forward slash indicates a fraction may be present. And a format of /?? is a fraction not translatable into HTML5. Turning these patterns into VBA, this is my MakeFracs() function. It checks that there is a slash, then that there is not a slash–digit–digit pattern, and finally that there is a “space–digits 1 through 7–slash–digits 2, 3, 4, 5, 6, 8″ pattern to screen out sevenths and ninths. If all of those pass, it substitutes in the &fracnd; formulation for the fraction.

Function MakeFracs(Arg As String) As String
   Dim sIN     As String
   Dim sOUT    As String
   Dim i As Long, j As Long
   Dim n As Long, d As Long
   Dim Fracs(1 To 15, 1 To 3) As String
   
   Fracs(1, 1) = "½": Fracs(1, 2) = "½": Fracs(1, 3) = "½"
   Fracs(2, 1) = "⅓": Fracs(2, 2) = "⅓": Fracs(2, 3) = "⅓"
   Fracs(3, 1) = "¼": Fracs(3, 2) = "¼": Fracs(3, 3) = "¼"
   Fracs(4, 1) = "⅕": Fracs(4, 2) = "⅕": Fracs(4, 3) = "⅕"
   Fracs(5, 1) = "⅙": Fracs(5, 2) = "⅙": Fracs(5, 3) = "⅙"
   Fracs(6, 1) = "⅛": Fracs(6, 2) = "⅛": Fracs(6, 3) = "⅛"
   Fracs(7, 1) = "⅔": Fracs(7, 2) = "⅔": Fracs(7, 3) = "⅔"
   Fracs(8, 1) = "⅖": Fracs(8, 2) = "⅖": Fracs(8, 3) = "⅖"
   Fracs(9, 1) = "¾": Fracs(9, 2) = "¾": Fracs(9, 3) = "¾"
   Fracs(10, 1) = "⅗": Fracs(10, 2) = "⅗": Fracs(10, 3) = "⅗"
   Fracs(11, 1) = "⅜": Fracs(11, 2) = "⅜": Fracs(11, 3) = "⅜"
   Fracs(12, 1) = "⅘": Fracs(12, 2) = "⅘": Fracs(12, 3) = "⅘"
   Fracs(13, 1) = "⅚": Fracs(13, 2) = "⅚": Fracs(13, 3) = "⅚"
   Fracs(14, 1) = "⅝": Fracs(14, 2) = "⅝": Fracs(14, 3) = "⅝"
   Fracs(15, 1) = "⅞": Fracs(15, 2) = "⅞": Fracs(15, 3) = "⅞"

   i = VBA.InStr(1, Arg, "/", vbTextCompare)
   If i = 0 Then   'there's no fraction
     MakeFracs = Arg
   ElseIf Mid$(Arg, i, 3) Like "/##" Then   'not HTML5
     MakeFracs = Arg
   ElseIf Mid$(Arg, i - 2, 4) Like " [1-7]/[234568]" Then
     sOUT = Mid$(Arg, i - 1, 3)
     n = VBA.Val(Left$(sOUT, 1))   'numerator
     d = VBA.Val(Right$(sOUT, 1))   'denominator
     If n < d Then
         If d Mod n = 0 Then
            d = d / n
            n = 1
         ElseIf d Mod 2 = 0 And n Mod 2 = 0 Then
            d = d / 2
            n = n / 2
         End If
         sIN = "&frac" & n & d & ";"
         For j = 1 To 15
            If Fracs(j, 1) = sIN Then
              sIN = Fracs(j, 2) '<-or Fracs(j, 3) for HEX
              Exit For
            End If
         Next j
         MakeFracs = VBA.Replace(Arg, sOUT, sIN)
      Else
         MakeFracs = Arg
      End If
   Else
      MakeFracs = Arg
   End If

End Function

 
At least that’s all I wanted it to do. In practice, Wikipedia and WordPress seem to be not fully onboard with HTML5 and do not handle all fifteen &fracnd; formats (I confirmed Firefox does). That added “j-loop” in the middle translates the &fracnd;’s into Dec code. This works fine, though it’s a step back from HTML5. Option is given to use Hex if desired. Your Excel table then looks like this:

N O P Q R S T U V
1 1/1 &#189; &#8531; &#188; &#8533; &#8537; 1/7 &#8539; 1/80
2 2/1 2/2 &#8532; &#189; &#8534; &#8531; 2/7 &#188; &#188;.
3 3/1 3/2 3/3 &#190; &#8535; &#189; 3/7 &#8540; &#8540;A
4 4/1 4/2 4/3 4/4 &#8536; &#8532; 4/7 &#189; 6 &#189;
5 5/1 5/2 5/3 5/4 5/5 &#8538; 5/7 &#8541; 7 &#8541;
6 6/1 6/2 6/3 6/4 6/5 6/6 6/7 &#190; 8 &#190;
7 7/1 7/2 7/3 7/4 7/5 7/6 7/7 &#8542; 9 &#8542;
8 8/1 8/2 8/3 8/4 8/5 8/6 8/7 8/8 10 8/8

 
Where N1: =MakeFracs(D1) filled down and right. Arranged that way you can see the HTML5 design thoughts. The very ugly website would look like this:

1/1 ½ ¼ 1/7 1/80
2/1 2/2 ½ 2/7 ¼ ¼.
3/1 3/2 3/3 ¾ ½ 3/7 ⅜A
4/1 4/2 4/3 4/4 4/7 ½ 6 ½
5/1 5/2 5/3 5/4 5/5 5/7 7 ⅝
6/1 6/2 6/3 6/4 6/5 6/6 6/7 ¾ 8 ¾
7/1 7/2 7/3 7/4 7/5 7/6 7/7 9 ⅞
8/1 8/2 8/3 8/4 8/5 8/6 8/7 8/8 10 8/8

 
Frankly, I’m not sure that this is an improvement. You’ll come across Wikipedia editors, however, who are convinced of it. I thought about adding a trailing space, as in MakeFracs = VBA.Replace(Arg, sOUT, sIN & Chr(32)), but for every time I wanted to, I thought of an example where I didn’t, and the logic got very convoluted. Better I decided to put the space in the table where wanted and not in the function. My HTML tablemaker is here, but it’s being overcome by the hard steady march of technology. Wiki and CSS tablemakers are coming up. I used MakeFracs() in the above. No fractions were harmed in the making of this post.

…mrt
©¿©¬

A ‘Price is Right’ Algorithm

You’ve probably watched The Price is Right TV game show. On it they run the Clock Game.

The game is played for two prizes. The actual price of the first prize is shown to the studio and home audiences. After the contestant gives their first bid, a 30 second clock is started and the host tells the contestant whether the actual price is higher or lower than the bid. The contestant continues to bid, responding to the host’s clues, until either the contestant wins by correctly guessing the price of the prize or the time expires. If time remains after the first prize is won, the process is repeated for the second prize.…With few exceptions, only prizes valued below $1,000 have traditionally been offered in the Clock Game.

The algorithm to use is simple:

  • Pick a ceiling ($1000) and a floor ($0)
  • Bid the average ($500)
  • If the bid is too low, the bid becomes the floor
  • If the bid is too high, the bid becomes the ceiling
  • Repeat

Being computer types, we’ll put the ceiling at $1024. For a $407-priced prize, your guesses would look like this:

  C D E F
1   407 <-- Price  
2 Floor BID   Ceiling
3 0 512 Too High 1024
4 0 256 Too Low 512
5 256 384 Too Low 512
6 384 448 Too High 512
7 384 416 Too High 448
8 384 400 Too Low 416
9 400 408 Too High 416
10 400 404 Too Low 408
11 404 406 Too Low 408
12 406 407 Stop 408

 
Ten guesses, and you’ve won a washing machine. Here are the formulas that make this work.

  • D1: = 407 (the unknown price)
  • C3: = 0 (the initial floor)
  • F3: = 1024 (the initial ceiling)
  • D3: = (C3+F3)/2 (512—the initial bid)
  • E3: = IF(D3=$D$1,”Stop”,IF(D3>$D$1,”Too High”,”Too Low”)) (the host’s clues)
  • C4: = IF(E3=”Stop”,”",IF(E3=”Too Low”,D3,C3))
  • D4: = IF(E3=”Stop”,”",(C4+F4)/2)
  • E4: = IF(D4=$D$1,”Stop”,IF(D4>$D$1,”Too High”,”Too Low”))
  • F4: = IF(E3=”Stop”,”",IF(E3=”Too High”,D3,F3))

Watch out for “curly quotes” if you copy and paste in. Filldown C4:F13. So what’s the point? We knew the “unknown price” going in. Here’s a recent prospective employee question the BBC got from Qualcomm:

Given 20 ‘destructible’ light bulbs (which break at a certain height), and a building with 100 floors, how do you determine the height the light bulbs break?

You watch The Price is Right or you read DDoE, and you think “Clock Game!” In 20 bulbs, if they break from a 407.407 foot drop, and a floor = 10 feet:

    407.407 <-- Break  
  Floor BID   Ceiling
1 0 512 Too High 1024
2 0 256 Too Low 512
3 256 384 Too Low 512
4 384 448 Too High 512
5 384 416 Too High 448
6 384 400 Too Low 416
7 400 408 Too High 416
8 400 404 Too Low 408
9 404 406 Too Low 408
10 406 407 Too Low 408
11 407 407.5 Too High 408
12 407 407.25 Too Low 407.5
13 407.25 407.375 Too Low 407.5
14 407.375 407.4375 Too High 407.5
15 407.375 407.4063 Too Low 407.4375
16 407.4063 407.4219 Too High 407.4375
17 407.4063 407.4141 Too High 407.4219
18 407.4063 407.4102 Too High 407.4141
19 407.4063 407.4082 Too High 407.4102
20 407.4063 407.4072 Too High 407.4082

 
You’re 2-ten-thousandths of a foot off. You get the job, a great way to start the new year.

 
…mrt
©¿©¬

Working with the MAXIF() and LARGEIF() functions

Oh, Wait! Excel doesn’t have those functions. We’ll have to roll our own. We’re going to use the Double Unary tip found here. In Excel, 1*TRUE = 1, 0+TRUE = 1, TRUE^1 = 1, and minus-minus TRUE (–TRUE, the double unary) = 1. It’s two sign changes, and thought to be the best way to turn an array of TRUE/FALSE into ones/zeros. Multiplying an array of parameters by an array of ones and zeros leaves only the TRUE parameters non-zero.

Back to our list of the 122 major teams in professional sports, the Boss has decided that sorting by name length, then alphabetically, isn’t going to do. He wants it by name length by stadium size. So, thanks to Wikipedia, your data looks like this, Column(F) is arena size:

  D E F
1 Anaheim Ducks 13 17,174
2 Arizona Cardinals 17 63,400
3 Arizona Diamondbacks 20 48,633
4 Atlanta Braves 14 50,097
5 Atlanta Falcons 15 71,228
6 Atlanta Hawks 13 18,729
7 Baltimore Orioles 17 45,363
8 Baltimore Ravens 16 71,008
9 Boston Bruins 13 17,565
10 Boston Celtics 14 18,624

Using fzz’s comment about the ROW() function, we’ll sort the length by G1=LARGE($E$1:$E$122,ROWS($G$1:$G1)) filled down. The table looks much like before:

  D E F G
1 Anaheim Ducks 13 17,174 29
2 Arizona Cardinals 17 63,400 22
3 Arizona Diamondbacks 20 48,633 22
4 Atlanta Braves 14 50,097 22
5 Atlanta Falcons 15 71,228 21
6 Atlanta Hawks 13 18,729 21
7 Baltimore Orioles 17 45,363 21
8 Baltimore Ravens 16 71,008 20
9 Boston Bruins 13 17,565 20
10 Boston Celtics 14 18,624 20

We know there are only three teams of name length 22. A formula – -($E$1:$E$122=22), using the double unary operation, gives us an array of 119 zeros, and just 3 ones that line up on 22. If we multiply that array by the array of arena capacities (F1:F122) we have 119 multiplications by zero, and three multiplications by 1. A MAX() function or an equivalent LARGE(,1) function on that array product returns the capacity of largest stadium hosting a team name 22 letters long. Those equations formulas, array entered, would look like this:

  • {=LARGE(- -($E$1:$E$122=22)*$F$1:$F$122,1)}
  • {=MAX(- -($E$1:$E$122=22)*$F$1:$F$122)}

We want to modify those for different teams. Changing the =22 to =$G1 is a start, and we’ve gone about as far as we can with our ‘MAXIF().’ It’s going to return the same thing three times. A scheme using COUNTIF() starting from the top and working down will improve the ‘LARGEIF()’. Using mixed references, $G$1:$G1 will grow as we fill down. COUNTIF($G$1:$G2,$G2) will only count one 22, COUNTIF($G$1:$G3,$G3) will count two 22s, and COUNTIF($G$1:$G4,$G4) will count 3, and that’s all there are. This array-entered equationformula then, filled down, is ‘LARGEIF()’:

  • {=LARGE(- -($E$1:$E$122=$G1)*$F$1:$F$122,COUNTIF($G$1:$G1,$G1))}

In I1 filldown =MATCH(H1,$F$1:$F$122,0) and in J1 filldown =INDEX(D:D,I1). Your table should look like this:

  D E F G H I J K
1 Anaheim Ducks 13 17,174 29 45,389 52 Los Angeles Angels of Anaheim  
2 Arizona Cardinals 17 63,400 22 19,980 95 Portland Trail Blazers  
3 Arizona Diamondbacks 20 48,633 22 19,356 63 Minnesota Timberwolves  
4 Atlanta Braves 14 50,097 22 18,144 30 Columbus Blue Jackets  
5 Atlanta Falcons 15 71,228 21 43,651 89 Philadelphia Phillies  
6 Atlanta Hawks 13 18,729 21 19,596 42 Golden State Warriors  
7 Baltimore Orioles 17 45,363 21 18,203 83 Oklahoma City Thunder  
8 Baltimore Ravens 16 71,008 20 68,756 69 New England Patriots  
9 Boston Bruins 13 17,565 20 67,164 49 Jacksonville Jaguars  
10 Boston Celtics 14 18,624 20 65,857 108 Tampa Bay Buccaneers  

Column(I) shows the indices reordered by size of the arena.

There are some problems with this approach. It only works with positive numbers, such as our capacities. Negative numbers will be less than a FALSE, and a zero will be returned in their place. And if ever capacities are equal, it will always return only the first (same old problem). Nicely here, hockey and basketball held in the same arena draw to different capacity. The file is available at http://wl.filegenie.com/~JMOprof/LargeIF.xls

…mrt
©¿©¬

The Twelve Days of Excel

When you carol along with The Twelve Days of Christmas do you think of spreadsheets? I do, and that certainly says all you need to know about my musical ability. The folks at PNC Wealth Management do also. Annually they issue their Christmas Price Index, with the 2011 version here. Spreading the data out, The Twelve Days of Christmas looks like this:

A B C D E F G H I J K L M N O P
1   1st
Day
2nd
Day
3rd
Day
4th
Day
5th
Day
6th
Day
7th
Day
8th
Day
9th
Day
10th
Day
11th
Day
12th
Day
Total
Quantity
Unit Cost Bundled Cost
2 Drummers Drumming                       12 12 $ 219.16 $ 2,629.90
3 Pipers Piping                     11 11 22 $ 220.69 $ 2,427.60
4 Lords-a-leaping                   10 10 10 30 $ 476.67 $ 4,766.70
5 Ladies Dancing                 9 9 9 9 36 $ 699.34 $ 6,294.03
6 Maids-a-milking               8 8 8 8 8 40 $ 7.25 $ 58.00
7 Swans-a-swimming             7 7 7 7 7 7 42 $ 900.00 $ 6,300.00
8 Geese-a-laying           6 6 6 6 6 6 6 42 $ 27.00 $ 162.00
9 Golden Rings         5 5 5 5 5 5 5 5 40 $ 129.00 $ 645.00
10 Calling Birds       4 4 4 4 4 4 4 4 4 36 $ 129.99 $ 519.96
11 French Hens     3 3 3 3 3 3 3 3 3 3 30 $ 50.00 $ 150.00
12 Turtle Doves   2 2 2 2 2 2 2 2 2 2 2 22 $ 62.50 $ 125.00
13 Partridge 1 1 1 1 1 1 1 1 1 1 1 1 12 $ 15.00 $ 15.00
14 Pear Tree 1 1 1 1 1 1 1 1 1 1 1 1 12 $ 169.99 $ 169.99

PNC provides the Bundled Cost. We have to work backwards to find the Unit Cost. In other words, eight total maids-a-milking cost $58. The data sources are at the Wiki link. The poor dairy lasses are working minimum wage.

For DDoE, PNC’s table is a good way to review the concept of named ranges. Define Christmas as =Sheet1!$B$2:$M$14, _12th_Day as =Sheet1!$M$2:$M$14, Bundled_Cost as =Sheet1!$P$2:$P$14, Maids as =Sheet1!$B$6:$P$6, Quantity as =Sheet1!$N$2:$N$14, and Unit_Cost as =Sheet1!$O$2:$O$14.

The total number of gifts can be =SUM(Christmas) or =SUM(Quantity), being 376 (more on this later). The cost of the 12th Day of Christmas can be =SUM(Bundled_Cost) or = SUMPRODUCT(_12th_Day,Unit_Cost), being $24,263.18. The total expenditure of your True Love is =SUMPRODUCT(Quantity,Unit_Cost), or $101,119.84. A very generous person, your True Love.

We can use the Intersection Operator, a space, as =Maids Quantity, to find that we need 40 work hours from them. The value of named ranges is in the legibility it brings to your spreadsheet. You can expand this until you cover all days and all gifts. The work is in the defining of the names, the benefit is in the presentation.

PNC says the total gifts number 364. Surely a partridge and a pear tree count as two gifts in your household, no? Happy Holidays!

…mrt
©¿©¬

Finding the 2nd, 3rd, 4th … MATCH()

Excel’s MATCH() function will return the relative location of the first occurrence that satisfies the match criterion (the lookup_value) within the specified array (the lookup_array).  Most often, this is what you want.  For instance, if you have duplicate entries, returning the first match is no different than returning the last match.  However, if you have unique entries with the duplicated criterion, how do you return all the unique values?

For example, you have a list of the 122 major professional sports teams (32 NFL, and 30 MLB, NBA, and NHL) sorted alphabetically in Column D.  In Column E you have code that returns the length of the team name, i.e. =LEN(D1).  Your data might look like this:

D E
1 Anaheim Ducks 13
2 Arizona Cardinals 17
3 Arizona Diamondbacks 20
4 Atlanta Braves 14
5 Atlanta Falcons 15
6 Atlanta Hawks 13
7 Baltimore Orioles 17
8 Baltimore Ravens 16
9 Boston Bruins 13
10 Boston Celtics 14

For no good reason we want to create a descending sort on name length. You can do it from the Sort selection, but we also want to keep the alpha-sort. In F1 you put =LARGE($E$1:$E$122,ROW()) and fill down. The ROW() function will cause the return of the first largest, then the second largest etc. Then you want to know how far down Column E those descending numbers fall, so that you can Index the name list that amount. In G1 put the code =MATCH(F1,$E$1:$E$122,0) and fill down. You’ll see from the data that the longest name is 29 letters, and it’s in ROW(52).

D E F G
1 Anaheim Ducks 13 29 52
2 Arizona Cardinals 17 22 30
3 Arizona Diamondbacks 20 22 30
4 Atlanta Braves 14 22 30
5 Atlanta Falcons 15 21 42
6 Atlanta Hawks 13 21 42
7 Baltimore Orioles 17 21 42
8 Baltimore Ravens 16 20 3
9 Boston Bruins 13 20 3
10 Boston Celtics 14 20 3

You can begin to see the problem: There are three teams with a 22-letter name, three with a 21-letter name, and at least 3 (in fact 13) with a 20-letter name. If in H1 we put =INDEX(D:D,G1) and filldown, we get:

D E F G H
1 Anaheim Ducks 13 29 52 Los Angeles Angels of Anaheim
2 Arizona Cardinals 17 22 30 Columbus Blue Jackets
3 Arizona Diamondbacks 20 22 30 Columbus Blue Jackets
4 Atlanta Braves 14 22 30 Columbus Blue Jackets
5 Atlanta Falcons 15 21 42 Golden State Warriors
6 Atlanta Hawks 13 21 42 Golden State Warriors
7 Baltimore Orioles 17 21 42 Golden State Warriors
8 Baltimore Ravens 16 20 3 Arizona Diamondbacks
9 Boston Bruins 13 20 3 Arizona Diamondbacks
10 Boston Celtics 14 20 3 Arizona Diamondbacks

This is exactly what we wanted to avoid. So, using Col(H) now as a helper column, in H1 put =G1. In H2 put

  • =IF(G2<>G1,G2,H1+MATCH(F2,INDIRECT(“E”&(H1+1)&”:E122″),0))

and fill down. If the value did not repeat, just copy it over; and if it didn’t, we’re making a “sliding range” that starts one row after the start of a repeated pair, ends at the end of the data, and “slides” down Column E. The sliding range is made by the INDIRECT() function. It builds the range one row down (H1+1) from the first of a repeating match. We add H1 to that result, and we have our index down Column D. This may be better seen in H3, adjacent to G3, where the first repeat starts.

  • =IF(G3<>G2,G3,H2+MATCH(F3,INDIRECT(“E”&(H2+1)&”:E122″),0))

The IF() is false. We are looking for the relative location of the lookup_value (22) in a new lookup_range (E31:E122). It’s 33 rows in. We add the original 30 to that, and the index is now 63. A similar thing happens in H4. H5 is G5 copied over. In Column I, I1 =INDEX(D:D,H1), filled down. The resulting table:

D E F G H I
1 Anaheim Ducks 13 29 52 52 Los Angeles Angels of Anaheim
2 Arizona Cardinals 17 22 30 30 Columbus Blue Jackets
3 Arizona Diamondbacks 20 22 30 63 Minnesota Timberwolves
4 Atlanta Braves 14 22 30 95 Portland Trail Blazers
5 Atlanta Falcons 15 21 42 42 Golden State Warriors
6 Atlanta Hawks 13 21 42 83 Oklahoma City Thunder
7 Baltimore Orioles 17 21 42 89 Philadelphia Phillies
8 Baltimore Ravens 16 20 3 3 Arizona Diamondbacks
9 Boston Bruins 13 20 3 49 Jacksonville Jaguars
10 Boston Celtics 14 20 3 53 Los Angeles Clippers

The names are now sorted by length, by alphabet. INDIRECT() is volatile, so there is a penalty for this. For extra credit, and no peeking, what’s the shortest name?

…mrt
©¿©¬

Is it Standard Time yet?

Part of my part-time job schedules world-wide PC-chat conferences weekly, and I announce the time referenced to the East Coast. As daylight savings time is about to end, I wanted an algorithm that knew whether standard time or daylight savings time was in effect.

As I’m not worried about the 2:00AM change over, I can do it based on the date. Since 2007, if the month is December through February, standard time is in effect. If the month is April through October, daylight savings time is in effect. Daylight savings time starts the second Sunday in March, and ends the first Sunday in November.

So, by counting Sundays in March and November, I can toggle the time zone. This is what I came up with.

Function TZ(SomeDay As String) As String
   Dim TestDay As Long, TestMonth As Long, TestYear As Long
   Dim IsDST   As Boolean
   Dim i As Long, SundayCount As Long

   TestDay = VBA.Day(SomeDay)
   TestMonth = VBA.Month(SomeDay)
   TestYear = VBA.Year(SomeDay)

   IsDST = False   ‘months 1, 2, 12
   Select Case TestMonth
      Case 3
         For i = 1 To TestDay
            If VBA.Weekday(VBA.DateSerial(TestYear, 3, i)) = 1 Then SundayCount = SundayCount + 1
            If SundayCount = 2 Then
               IsDST = True
               Exit For
            End If
         Next i
      Case 4, 5, 6, 7, 8, 9, 10
         IsDST = True
      Case 11
         For i = 1 To TestDay
            If VBA.Weekday(VBA.DateSerial(TestYear, 11, i)) = 1 Then SundayCount = SundayCount + 1
            If SundayCount = 1 Then Exit For
         Next i
         If SundayCount = 0 Then IsDST = True
   End Select
   If IsDST Then TZ = “EDT” Else TZ = “EST”
End Function

The default WEEKDAY() function returns 1 for Sunday, so if there are 2 Sundays in March including the day in question, turn IsDST to TRUE, and stop the loop. Conversely, if there is 1 Sunday in November including the day in question, leave IsDST as FALSE, and stop the loop.

I use the VBA. leader to ensure this function works on a Mac. Needless to say, it also works in MSWord.

…mrt
©¿©¬

Opening Paint files in XL

The Great Wave of Kanagawa
Specifically, MacPaint files. Why MacPaint? 1. With a maximum size of 576 pixels wide by 720 pixels tall, MacPaint images fit on a modern spreadsheet when using cells as pixels, and 2. MacPaint prototyped the lossless PackBits compression algorithm, which is the default compression scheme for TIFF.

PackBits is a signed-byte file structure with flag-counter bytes interspersed with data bytes. If the flagbyte is negative, the next byte (always a data byte) is repeated, zero-based, the two’s-complement of the flagbyte times. If the flagbyte is a positive number, the next number of bytes are copied directly. Each data byte represents the status of eight pixels, with 1 indicating a black pixel and 0 representing a white pixel, as shown below.

The PackBits algorithm is defined here. The following code, cross-posted to Wikipedia, handles Apple’s demonstration case.

Sub UnpackBitsDemo()

   Dim File    As Variant
   Dim MyOutput As String
   Dim Count   As Long
   Dim i As Long, j As Long
 
   File = “FE AA 02 80 00 2A FD AA 03 80 00 2A 22 F7 AA”
   File = Split(File, ” “)
   
   For i = LBound(File) To UBound(File)
      Count = Application.WorksheetFunction.Hex2Dec(File(i))
      Select Case Count
      Case Is >= 128
         Count = 256 – Count ‘Two’s Complement
         For j = 0 To Count ‘zero-based
            MyOutput = MyOutput & File(i + 1) & ” “
         Next j
         i = i + 1 ‘Adjust the pointer
      Case Else
         For j = 0 To Count ‘zero-based
            MyOutput = MyOutput & File(i + j + 1) & ” “
         Next j
         i = i + j ‘Adjust the pointer
      End Select
   Next i
   Debug.Print MyOutput
   ’AA AA AA 80 00 2A AA AA AA AA 80 00 2A 22 AA AA AA AA AA AA AA AA AA AA’
End Sub

It works out then that a PackBits stream is a flagbyte followed by data bytes followed by a flagbyte followed by data bytes… Each flagbyte indirectly points to the location of the next flagbyte.
 
Excel’s VBA does not have signed bytes, but if we look at the flagbyte’s value, and “Select Case” all flagbytes greater or equal to 128 (b10000000) we have a test that handles negative flag bytes. The two’s complement of a number N is 2B-N, where B is the word size, in our case, 8. In other words, the two’s-complement of the negative flagbyte is 256 minus the value of the flagbyte.
 
The MacPaint file has a 512-byte header that we can ignore. It also can have, as data bytes, internal bytes that would otherwise be read as end-of-file. We handle that by reading the file in for binary access.
 
The basic approach is to read in the file similarly to a text file. Turn the flagbytes characters into ascii, with the ascii as the count to copy or to take the two’s complement of. Byte(513) has to be a flagbyte. The data bytes are input as characters, turned to ascii, then turned to byte-strings. The cells are turned black or white by looping through the byte-string. After 576 1′s and 0′s (or 72 data bytes) a new row is started. The file structure does not reveal the length of the data, but we can infer that from the file’s length.

Sub OpenMacPaint()
   Dim TotalBytes As Long
   Dim Buffer As String
   Dim File    As Variant
   Dim Char  As String * 1
   Dim NextChar As String * 1
   Dim NextInt As Integer
   Dim NextByte As String * 8
   Dim Count   As Long
   Dim i As Long, j As Long, r As Long, c As Long, b As Long
   Dim Rng As Range
   
   File = Application.InputBox(“Enter the full path to the MacPaint file.”, “Path to the MacPaint file…”, _
      “Macintosh HD:Users:User:Downloads:GREAT WAVE.mac”, , , 2) ‘ Your path here
   If File = False Then Exit Sub

   Open File For Binary Access Read As #1
   TotalBytes = FileLen(File)
   Buffer = Input(TotalBytes, #1)
   Close #1
   If TotalBytes = 0 Then
      MsgBox “Exiting!”, vbCritical + vbOKOnly, “File not found!”
      Exit Sub
   End If
   
   c = 1
   r = 1
   Application.ScreenUpdating = False
   For i = 513 To TotalBytes ‘skip the header
      Char = VBA.Mid$(Buffer, i, 1)
      Count = Asc(Char)
      Select Case Count
         Case Is >= 128
            Count = 256 – Count ‘Two’s Complement
            NextChar = VBA.Mid$(Buffer, i + 1, 1)
            NextInt = Asc(NextChar)
            NextByte = Application.WorksheetFunction.Dec2Bin(NextInt, 8)
            For j = 0 To Count ‘zero-based repeat of the next byte
                For b = 1 To 8
                    If VBA.Mid$(NextByte, b, 1) = “1″ Then
                        Worksheets(“Sheet4″).Cells(r, c).Interior.ColorIndex = 1 ‘Black
                    Else
                        Worksheets(“Sheet4″).Cells(r, c).Interior.ColorIndex = 2 ‘White
                    End If
                    c = c + 1
                    If c > 576 Then ‘a new row
                        c = 1
                        r = r + 1
                    End If
                Next b
            Next j
            i = i + 1 ‘adjust the counter
         Case Else
            For j = 0 To Count ‘zero-based copy of Count bytes
                NextChar = VBA.Mid$(Buffer, i + j + 1, 1)
                NextInt = Asc(NextChar)
                NextByte = Application.WorksheetFunction.Dec2Bin(NextInt, 8)
                For b = 1 To 8
                    If VBA.Mid$(NextByte, b, 1) = “1″ Then
                        Worksheets(“Sheet4″).Cells(r, c).Interior.ColorIndex = 1
                    Else
                        Worksheets(“Sheet4″).Cells(r, c).Interior.ColorIndex = 2
                    End If
                    c = c + 1
                    If c > 576 Then ‘a new row
                         c = 1
                         r = r + 1
                    End If
                Next b
            Next j
            i = i + j ‘adjust the counter
         End Select
   Next i
   
   Set Rng = Worksheets(“Sheet4″).Range(“A1:VE720″)
   Rng.ColumnWidth = 1
   Rng.RowHeight = 12
   ActiveWindow.Zoom = 10
   Application.ScreenUpdating = True
Exit Sub

What I don’t like about the code is that for the case of Count>=128, the repetitive bytes, I’m parsing it Count times. Rather I should parse it once and copy it Count times. I just don’t see how. I know somebody out there does.
 
If you want the MacPaint copy of Hokusai’s The Great Wave off Kanagawa shown above, it’s here. A nice thing about the Excel code is that you can change the color indices to suit. An appropriate blue might be color index 32, RBG(0,0,255).

To show why PackBits is still used, the lossless JPEG used above for HTML purposes is 211 Kbytes. The lossless Mac counterpart is 17 Kbytes. While a MacPaint file can define 576*720 pixels (0.4 megapixels), its maximum size is 512 + (72*90) bytes, or just over 51 Kbytes.

 
…mrt

Change Case… ala’ Microsoft Word

Mac VersionLast week, more absent-minded than usual, I forgot the software I was in. I looked for the Change Case … format dialog in Excel. It’s not there, of course. It’s in Word. It’s in PowerPoint. It’s not in Excel. So I took a morning and wrote one.

Word gives you five options to change case: Sentence case, lower case, UPPER CASE, Title Case, and tOGGLE cASE. Word’s “Title Case” is a misnomer. It’s really what we think of as PROPER() case–every word capitalized. So my version has six options, including a true Title Case in accordance with the government style manual for capitalization of short words, and a Proper Case that does exactly as MSWord does in its Title Case.

Considering The Grapes of Wrath as illustrative:

  • Sentence case returns: The grapes of wrath
  • lower case returns: the grapes of wrath
  • UPPER CASE returns: THE GRAPES OF WRATH
  • Title Case returns: The Grapes of Wrath
  • tOGGLE cASE returns: tHE gRAPES OF wRATH
  • Proper Case returns: The Grapes Of Wrath. Note the captialized of.

PC verisonLower case, upper case, and proper case were easy. They are just applications of LCase(), UCase(), or Application.Worksheetfunction.Proper() to the selection contents.

Title case starts with capitalizing every word and then swapping in the lower case versions of the one, two, and three letter words, when found between two spaces. Two spaces protects against an inappropriate changeout of a leading article.

Sentence case is a recursive search for full-stop punctuation (periods, exclamation points, question marks, and colons) reading left to right. The exit condition is when there nothing more found to the right.

Toggle case adds or subtracts 32 to the ascii-code of each letter to change its case. Upper to lower, add 32. Lower to upper, subtract 32. Non-letters are unchanged. The Like operator is used to for the comparisons in both the sentence and toggle cases.

The macro module code is a one-liner. All the execution is in the form’s code.

Sub Change_Case()
   frmChangeCase.Show
End Sub

Code for the form:

Private CaseChange As Long
Option Explicit

Private Sub CommandButton1_Click()   ‘Cancel
   Unload Me
   End
End Sub

Private Sub CommandButton2_Click()   ‘OK
   Me.Hide
   Change_Case_1
End Sub

Private Sub OptionButton1_Click()   ‘Sentence case
   CaseChange = 1
End Sub

Private Sub OptionButton2_Click()   ‘lower case
   CaseChange = 2
End Sub

Private Sub OptionButton3_Click()   ‘UPPER CASE
   CaseChange = 3
End Sub

Private Sub OptionButton4_Click()   ‘Title Case
   CaseChange = 4
End Sub

Private Sub OptionButton5_Click()   ‘tOGGLE cASE
   CaseChange = 5
End Sub

Private Sub OptionButton6_Click()   ‘Proper Case
   CaseChange = 6
End Sub

Private Sub UserForm_Initialize()
   OptionButton4 = True
   CaseChange = 4
End Sub

Private Sub Change_Case_1()
‘http://www.writers.com/tips_titles.html
‘Capitalize all words in titles of publications and documents, except
‘a, an, the, at, by, for, in, of, on, to, up, and, as, but, it, or, and nor.
   Dim Cell    As Range
   Dim i       As Long
   Dim Replacement As String
   Dim SubOut  As Variant
   Dim SubIn   As Variant

   SubIn = Split(“a an the at by for in of on to up and as but it or nor”)
   SubOut = Split(“A An The At By For In Of On To Up And As But It Or Nor”)
   
   For i = LBound(SubIn) To UBound(SubIn)
      SubIn(i) = VBA.Chr$(32) & SubIn(i) & VBA.Chr$(32)
      SubOut(i) = VBA.Chr$(32) & SubOut(i) & VBA.Chr$(32)
   Next i

   For Each Cell In Selection
      If Application.WorksheetFunction.IsText(Cell.Value) Then
         Select Case CaseChange
            Case 1   ‘The grapes of wrath
               For i = 1 To Len(Cell.Text)
                  If Mid$(Cell.Text, i, 1) Like “[A-Za-z0-9]” Then
                     Replacement = UCase(Left$(Cell.Text, i)) & SentenceCase(Right$(Cell.Text, Len(Cell.Text) – i))
                     ’ Recursive
                     Exit For
                  End If
               Next i
               Cell.Value = Replacement
            Case 2   ‘the grapes of wrath
               Cell.Value = VBA.LCase(Cell.Value)
            Case 3   ‘THE GRAPES OF WRATH
               Cell.Value = VBA.UCase(Cell.Value)
            Case 4   ‘The Grapes of Wrath
               Replacement = Application.WorksheetFunction.Proper(Cell.Value)
               For i = LBound(SubOut) To UBound(SubOut)
                  Replacement = VBA.Replace(Replacement, SubOut(i), SubIn(i), vbBinaryCompare)
               Next i
               Cell.Value = Replacement
            Case 5   ‘ tHE gRAPES oF wRATH
               Replacement = “”
               For i = 1 To Len(Cell.Text)
                  If VBA.Mid$(Cell.Text, i, 1) Like “[A-Z]” Then
                     Replacement = Replacement & VBA.Chr$(VBA.Asc(VBA.Mid$(Cell.Text, i, 1)) + 32)
                  ElseIf VBA.Mid$(Cell.Text, i, 1) Like “[a-z]” Then
                     Replacement = Replacement & VBA.Chr$(VBA.Asc(VBA.Mid$(Cell.Text, i, 1)) – 32)
                  Else
                     Replacement = Replacement & VBA.Mid$(Cell.Text, i, 1)
                  End If
               Next i
               Cell.Value = Replacement
            Case 6   ‘The Grapes Of Wrath
               Cell.Value = Application.WorksheetFunction.Proper(Cell.Value)
         End Select
      End If
   Next Cell
   Unload Me
End Sub

Private Function SentenceCase(Sentence As String) As String
   Dim i As Long, j As Long, n As Long
   Dim LeftSide As String, RightSide As String

   n = Len(Sentence)
   For i = 1 To n
      If Mid$(Sentence, i, 2) Like “[.!?:] ” Then
         LeftSide = Left$(Sentence, i + 1)
         RightSide = Right$(Sentence, n – i – 1)
         Exit For
      ElseIf i = n Then
         LeftSide = Sentence
         RightSide = vbNullString   ‘Exit condition
      End If
   Next i

   For j = 1 To Len(RightSide)
      If Mid$(RightSide, j, 1) Like “[A-Za-z0-9]” Then
         RightSide = UCase(Left$(RightSide, j)) & SentenceCase(Right$(RightSide, Len(RightSide) – j))
         ’Recursive
         Exit For
      End If
   Next j

   SentenceCase = LeftSide & RightSide

End Function

I wanted to use the antique char|32 and char&32 to do the toggle-case change, but I couldn’t find the right syntax. Wiki says it’s not any faster these days, but readers may want to comment.

Tested on a PC and a Mac (using XL2011). The Mac does not appear to support tool-tips from the form. The Mac version looked better in Tahoma 12, the PC version in Tahoma 10. I compromised.

The form is available here.

…mrt

Decimal Degrees to Degrees-Mins-Secs

GPS tells you where you are in Decimal Degrees (DD), with positive degrees being north latitude or east longitude, and negative degrees being south latitude or west longitude. Most charts (I was a nautical type before I was an Excel type. Think maps if that’s not you) tell you position in degrees-arc minutes-arc seconds (DMS). While one can “eyeball” the conversion from DD to DMS, let’s get more specific. Thanks to Wikipedia, the US Capitol is at 38.889722°, -77.008889°. Ignoring what we might otherwise know, by the GPS sign conventions the Capitol has a north latitude and a west longitude. Making the conversion is about handling remainders. Walking through converting the Capitol’s longitude, the degrees are:

  • D = INT(ABS(-77.008889)) or 77

The remainder R is:

  • R = ABS(-77.008889) – D
  • R = 77.008889 – 77
  • R = 0.008889

To convert to arc-minutes:

  • M = R*60
  • M = 0.008889*60
  • M = 0.533340
  • M = INT(M) = INT(0.533340)
  • M = 0, but always formatted “00″

The remainder is:

  • R = 0.533340 – M*60
  • R = 0.533340 – 0*60
  • R = 0.533340

To convert to arc-seconds:

  • S = R*60
  • S = 0.533340*60
  • S = 32.000400
  • S = INT(S) = INT(32.000400)
  • S = 32

The Capitol is at 77 degrees, 00 minutes, 32 seconds, and remembering the minus sign, west. Or 77° 00′ 32? W. Alternatively, 77° 00′ 32″ W with ascii substitutions for prime and double prime.

Turning that into VBA, with an optional Boolean to specify if you want north/south or east/west, and another option for Ascii representation, the DD2DMS conversion looks like this:

Function DD2DMS(Degrees As Variant, Optional Lat As Boolean = True, _
   Optional Ascii As Boolean = True) As String
   Dim ArcMins As Variant
   Dim ArcSecs As Variant
   Dim NSEW    As String * 2
   Dim D_mark As String * 1, M_mark As String * 1, S_mark As String * 1

   If Lat Then
      NSEW = IIf(Degrees < 0, ” S”, ” N”)
   Else
      NSEW = IIf(Degrees < 0, ” W”, ” E”)
   End If
   
   D_mark = ChrW(176)
   If Ascii Then
      M_mark = ChrW(39): S_mark = ChrW(34)
   Else
      M_mark = ChrW(&H2032): S_mark = ChrW(&H2033)
   End If
   
   Degrees = Abs(Degrees)
   ArcMins = 60 * (Degrees – Int(Degrees))
   ArcSecs = 60 * (ArcMins – Int(ArcMins))
   Degrees = Int(Degrees) & D_mark & Chr(32)
   ArcMins = Format(Int(ArcMins), “00″) & M_mark & Chr(32)
   ArcSecs = Format(ArcSecs, “00″) & S_mark & NSEW
   
   DD2DMS = Degrees & ArcMins & ArcSecs
End Function

The degree character is ascii(176). That’s actually more concise than my explanation. ;-(

Can it be done in a spreadsheet formula? Of course, but it’s complicated, and that’s the reason for the somewhat tedious example above to walk through. Formulas do modular arithmetic differently than VBA. In a formula MOD(ABS(-77.008889),1) equals 0.008889. In VBA, ABS(-77.008889) Mod 1 equals zero. We’ll make use of this difference to find our remainders. Assuming the Capitol’s decimal longitude is in H8, then in four parts:

D = INT(ABS(H8))&CHAR(176)

Arc-minutes:

M = TEXT(INT(MOD(ABS(H8),1)*60),” 00″)&CHAR(39)

Arc-seconds:

S = TEXT(MOD(MOD(ABS(H8),1)*60,1)*60,” 00″)&CHAR(34)

Note the nested MOD(,1)’s to get the second remainder. Lastly for east/west:

EW = IF(H8 < 0,” W”,” E”)

The longitude is the concatenation of all of these:

=INT(ABS(H8))&CHAR(176)&TEXT(INT(MOD(ABS(H8),1)*60),” 00″)&CHAR(39)
&TEXT(MOD(MOD(ABS(H8),1)*60,1)*60,” 00″)&CHAR(34)&IF(H8 < 0,” W”,” E”)

Complicated. Can it be done as a number format? Since we are talking minutes and seconds suggests yes. However Excel doesn’t process negative times, so the answer will be completely right only for that quarter of the world that is in the northern latitudes and eastern longitudes. Assuming again H8 holds the longitude, than in another cell, =ABS(H8/24). Custom format that cell as

  • [h]° mm’ ss”

You enter the degree symbol by holding down the alt-key and keying 0176 on the keypad. How to get the north/south or east/west right will require another cell.

There’s two and a quarter ways to make the DD2DMS conversion. A second of angle on the surface of the earth is about 30 meters or 100 feet. … Degrees, given to three decimal places (1/1000 of a degree), have about 1/4 the precision as degrees-minutes-seconds (1/3600 of a degree), and so identify locations within about 120 meters or 400 feet. That’s not true as you add decimal places, of course, and the decimal to sexagesimal (Base10 to Base60) conversion becomes inexact.

More in this in the next post: How to go DMS2DD.

Unicode and VBA’s ChrW() and AscW() functions

Spreadsheets have their CHAR() function, and VBA has its Chr() function. Both return the text character for the specified numerical input, 0 to 255. And spreadsheets have their CODE() function, and VBA has its Asc() function. Both of those return the ASCII code for the leading character of a text string. All well-worn stuff.

But what if you want or need to work with Unicode values? All four functions fail you. As an example, assume you want the true prime character (‘, Unicode 2032) in a string. The prime character, technically, is not an italicized apostrophe (), a right single curly quote (‘), or an acute accent (‘).

VBA provides the ChrW() function that does that. ChrW() expects a long as input, but also accepts hexadecimal. Unicode is in hex numbering, so there are two choices: Change U2032 to decimal, or tell ChrW() that the input is in Hex. Since HEX2DEC(2032) is 8242, these two are equivalent:

  • ChrW(8242)
  • ChrW(&H2032)

Both will put ‘ into a string. If ChrW() repeated the same functionality of Chr() below 256, things would be simple. However, the Windows character set deviates from the Unicode character set for ASCII(128) to ASCII(159). In that range, Chr(CharCode) and ChrW(CharCode) produce different results. As WikiPedia says, Windows “coincides with ISO-8859-1 for all codes except the range 128 to 159 (hex 80 to 9F), where the little-used C1 controls are replaced with additional characters.” Not sure what C1 controls (probably a printer), but if we want to get Unicode to the spreadsheet, do we want it to give the functionality of CHAR()/Chr(), or that of ChrW() which is ISO-8859-1 compliant? Or, optionally both. The function CHARW() takes the optional route. If you set Exact_functionality to TRUE, you can put those C1 controls in your spreadsheet. The default is to do otherwise.

Function CHARW(CharCode As Variant, Optional Exact_functionality As Boolean = False) As String
‘Use a Leading “U” or “u” to indicate Unicode values
‘Exact_functionality returns the Unicode characters for Ascii(128) to Ascii(159) rather than
‘the Windows characters

   If UCase(Left$(CharCode, 1)) = “U” Then CharCode = Replace(CharCode, “U”, “&H”, 1, 1, vbTextCompare)
   CharCode = CLng(CharCode)

   If CharCode < 256 Then
      If Exact_functionality Then
         CHARW = ChrW(CharCode)
      Else
         CHARW = Chr(CharCode)
      End If
   Else
      CHARW = ChrW(CharCode)
   End If
End Function

One very nice thing is that you can feed Clng() a hex value, and it will do the HEX2DEC conversion for you.

The VBA function AscW() goes the other way, and has the same ISO problems. It will tell you the decimal code of the first character in a Unicode string, with no regard to the Windows character set. We can make another UDF CODEW() that can optionally specify either the decimal or hex value for the first character is returned, and whether or not to be ISO compliant. The default is to return the HEX unicode (as Uxxxx) and not to comply.

Function CODEW(Character As String, Optional Unicode_value As Boolean = True, _
               Optional Exact_functionality As Boolean = False) As Variant
‘ Exact Functionality returns exact Unicode for characters as AscW() does
‘ rather than Windows characters as Asc() does
  Dim Characters As String
   Dim i       As Long

   If Exact_functionality Then
      CODEW = AscW(Character)
      If Unicode_value Then CODEW = “U” & Hex(CODEW)
      Exit Function
   End If

   For i = 128 To 159 ‘where non-compliant
     Characters = Characters & Chr(i)
   Next i

   If InStr(1, Characters, Left$(Character, 1), vbBinaryCompare) Then
      CODEW = Asc(Character)
   Else
      CODEW = AscW(Character)
   End If
   If Unicode_value Then CODEW = “U” & Hex(CODEW)
End Function

The default will return U2032 when the first character is ‘, and 8242 when Unicode_value is set FALSE. For another example, € is CHAR(128), Chr(128), ChrW(8354), CHARW(128), CHARW(“U80″), CHARW(“U20AC”,TRUE) and CHARW(8364,TRUE).

CODEW(“€”) is “U80″, CODEW(“€”,FALSE) is 128, CODEW(“€”,,TRUE) is “U20AC”, and CODEW(“€”,FALSE,TRUE) is 8354.

To see Unicode characters, the cell’s font has to be set to a Unicode font.

…mrt