146

I was solving some problem on codeforces. Normally I first check if the character is upper or lower English letter then subtract or add 32 to convert it to the corresponding letter. But I found someone do ^= 32 to do the same thing. Here it is:

char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a

I have searched for an explanation for this and didn't find out. So why this works?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Devon
  • 1,253
  • 2
  • 7
  • 4
  • 5
    https://en.wikipedia.org/wiki/File:USASCII_code_chart.png Tip: you can convert `@` into \` by using `^ 32`. – KamilCuk Feb 05 '19 at 14:17
  • 113
    FWIW, it doesn't really "work". It works for this particular character set but there are other sets where it won't You should use `toupper` and `tolower` to switch cases. – NathanOliver Feb 05 '19 at 14:17
  • 7
    sometime with online contests "the idea" is to write code in such an obfuscated way that it would never pass a serious review ;) – 463035818_is_not_an_ai Feb 05 '19 at 14:18
  • 21
    ^= is transforming the value using XOR. Uppercase ASCII letters have a zero in the corresponding bit, while lowercase letters have a one. That said, please don't! Use proper character (unicode) routines to convert between lowercase and uppercase. The era of just ASCII is long gone. – Hans-Martin Mosner Feb 05 '19 at 14:21
  • what @NathanOliver said. This is cute but its going to cause a bug. – StayOnTarget Feb 05 '19 at 20:06
  • 14
    It's not just that it only works with some character sets. Even if we assume all the world is UTF-8 (which might at least be a nice utopian goal), it also only works with the 26 letters `A` to `Z`. That's fine as long as you only care about English (and don't use spellings "naïve", words like "café", or names with diacritics...), but the world is not just English. – ilkkachu Feb 05 '19 at 20:30
  • Related: you can *check* for an alphabetic ASCII character by forcing lowercase with `|= 0x20` and then checking (unsigned) `if(c - 'a' < ('a'-'z'))`. So just 3 operations: OR + SUB + CMP. See also [Convert a String In C++ To Upper Case](//stackoverflow.com/a/37151084) (SIMD string toupper masking the operand for XOR) and [How to access a char array and change lower case letters to upper case, and vice versa](//stackoverflow.com/a/35936844) (C with SIMD intrinsics, and scalar x86 asm case-flip for alphabetic characters, leaving others unmodified.) – Peter Cordes Feb 05 '19 at 23:32
  • 3
    Even if [very hypothetically] this was less fragile and _did_ work with more character sets, etc., I'd still discourage using it. There are standard ways to do this, so try to take advantage of the tools that already exist. Inventing your own "clever" way to do it is a great way to end up with unreadable/unmaintainable code which is really hard to debug when you DO eventually find the case that breaks your implementation. (Just a general comment and not an attack on OP, who really only asked **why** this works) – A C Feb 06 '19 at 06:06
  • 3
    I can't tell from the question if you know what the `^` and `^=` operators do in the first place, but it's relevant to answering your question well. Do you? – Useless Feb 06 '19 at 13:12
  • As an aside the XOR/OR etc. is a very common method for EBCDIC case toggling and fiddling. I would not try this with DBCS nor Unicode. Casting to int for arithmetic has largely fallen out of favour for many good reasons. – mckenzm Feb 06 '19 at 22:01
  • In follow up to @NathanOliver's comment - things that appear to work for a subset of things, but don't assert that they only get provided that subset is begging for a bug in the future ... and you'll never know it. If you don't want to use std::toupper/tolower (because maybe your platform doesn't offer the std library) then you should at the very least assert that you're looking at [a-zA-Z] – UKMonkey Feb 07 '19 at 11:53
  • @NathanOliver: TBH `toupper` and `tolower` are hopelessly broken in any multibyte encoding, such as the oh-so-rarely-used UTF-8. It might have been a solution maybe in the 80s, but today I'd argue is probably even worse than `^32`. – Matteo Italia Feb 07 '19 at 19:13
  • 2
    Possible duplicate of [How does s\[i\]^=32 convert upper to lower case?](https://stackoverflow.com/questions/40641468/how-does-si-32-convert-upper-to-lower-case) – alain Feb 08 '19 at 10:36
  • I've always known this trick as `^= ' '` – Alice Ryhl Feb 08 '19 at 12:06
  • 1
    @AliceRyhl The "trick" I used was `^= 'A' ^ 'a'` when `toupper()` was not available and needed tight code. – chux - Reinstate Monica Feb 08 '19 at 15:15
  • It just works for the standard ASCII table where there is a distance of 32 between Uppercase alphabets and a lowercase alphabets. It does not take into account the locale or the extended ASCII table. In the basic table, you can use this to go from e to E or vice-versa. But in the fr_FR locale, all the variations of e should map to E when capitalized but in fr_CA, the accents remain. That means that UpperCase("eéèëê") ==> "EEEEE" in fr_FR ==> "EÉÈËÊ" in fr_CA. – asiby Mar 04 '19 at 15:55
  • ASCII was designed this way to make common operations like case swapping, printing numbers, etc. as easy as possible for the CPU. Upper case and lower case letters were purposely placed 32 apart so that you could change a letter from upper case to lower case by toggling a single bit. – puppydrum64 Jan 18 '23 at 12:01

10 Answers10

147

Let's take a look at ASCII code table in binary.

A 1000001    a 1100001
B 1000010    b 1100010
C 1000011    c 1100011
...
Z 1011010    z 1111010

And 32 is 0100000 which is the only difference between lowercase and uppercase letters. So toggling that bit toggles the case of a letter.

Hanjoung Lee
  • 2,123
  • 1
  • 12
  • 20
  • 50
    "toggles the case" *only for ASCII – Mooing Duck Feb 06 '19 at 00:49
  • 39
    @Mooing only for A-Za-z in ASCII. Lower case of "[" is *not* "{". – dbkk Feb 06 '19 at 04:00
  • 22
    @dbkk `{` is shorter than `[`, so it is a "lower" case. No? Ok, I'll show myself out :D – Peter Badida Feb 06 '19 at 10:35
  • 27
    Trivia tidbit: In the 7 bit area, German computers had [\]{|} remapped to ÄÖÜäöü since we needed Umlauts more than those characters, so in that context, { (ä) actually *was* the lowercase [ (Ä). – Guntram Blohm Feb 07 '19 at 11:33
  • 15
    @GuntramBlohm Further trivia tidbit, this is why [IRC servers consider](https://tools.ietf.org/html/rfc1459.html#section-2.2) `foobar[]` and `foobar{}` to be identical nicknames, as nicknames are case *insensitive*, and IRC has its origins in Scandinavia :) – ZeroKnight Feb 07 '19 at 23:14
  • 1
    The phrase worth knowing is "ISO 646". Just as in the 8-bit era there were many national/regional ASCII supersets, in the 7-bit era ASCII was just one of many character sets that were 646 compatible. And thus, the ^= 32 trick actually works for (most?) ISO 646-based character sets, not just ASCII :D – Andrea Feb 09 '19 at 21:07
117

This uses the fact than ASCII values have been chosen by really smart people.

foo ^= 32;

This flips the 6th lowest bit1 of foo (the uppercase flag of ASCII sort of), transforming an ASCII upper case to a lower case and vice-versa.

+---+------------+------------+
|   | Upper case | Lower case |  32 is 00100000
+---+------------+------------+
| A | 01000001   | 01100001   |
| B | 01000010   | 01100010   |
|            ...              |
| Z | 01011010   | 01111010   |
+---+------------+------------+

Example

'A' ^ 32

    01000001 'A'
XOR 00100000 32
------------
    01100001 'a'

And by property of XOR, 'a' ^ 32 == 'A'.

Notice

C++ is not required to use ASCII to represent characters. Another variant is EBCDIC. This trick only works on ASCII platforms. A more portable solution would be to use std::tolower and std::toupper, with the offered bonus to be locale-aware (it does not automagically solve all your problems though, see comments):

bool case_incensitive_equal(char lhs, char rhs)
{
    return std::tolower(lhs, std::locale{}) == std::tolower(rhs, std::locale{}); // std::locale{} optional, enable locale-awarness
}

assert(case_incensitive_equal('A', 'a'));

1) As 32 is 1 << 5 (2 to the power 5), it flips the 6th bit (counting from 1).

YSC
  • 38,212
  • 9
  • 96
  • 149
  • 16
    EBCDIC was chosen by some very smart people too: works really nicely on punched cards cf. ASCII which is a mess. But this is a nice answer, +1. – Bathsheba Feb 05 '19 at 14:35
  • 1
    @Bathsheba ASCII on punchcard? Who would? :D – YSC Feb 05 '19 at 14:37
  • 66
    I don't know about punch cards, but ASCII *was* used on paper tape. That's why the Delete character is encoded as 1111111: So you could mark any character as "deleted" by punching out all the holes in its column on the tape. – dan04 Feb 05 '19 at 17:54
  • 24
    @Bathsheba as someone who hasn't used a punchcard, it's very difficult to wrap my head around the idea that EBCDIC was intelligently designed. – Lord Farquaad Feb 05 '19 at 18:48
  • May I recommend re-checking which bit gets flipped? –  Feb 05 '19 at 20:03
  • 9
    @LordFarquaad IMHO the Wikipedia picture of how letters are written on a punchcard is an obvious illustration on how EBCDIC does make some (but not total, see / vs S) sense for this encoding. https://en.wikipedia.org/wiki/EBCDIC#/media/File:Blue-punch-card-front-horiz_top-char-contrast-stretched.png – Peteris Feb 05 '19 at 21:25
  • The notice is incorrect. Even the reference page for std::tolower states so. Some characters have multiple equivalent forms - this solution will not work for them. It will also not handle for example 'á' and 'Á', even though it will accept them. Neither version is "portable." –  Feb 05 '19 at 22:01
  • 1
    And *any* one-character-at-a-time approach to case-folding will fail for, e.g., German `ß` and `SS`. – dan04 Feb 05 '19 at 23:35
  • 6
    _"This trick only works on ASCII platforms."_ - True, a similar `^= 64` will work for EBCDIC though! (But not for ASCII anymore) – marcelm Feb 05 '19 at 23:46
  • 12
    @dan04 Note to mention "what is the lower-case form of 'MASSE'?". For those that don't know, there are *two* words in German whose upper case form is MASSE; one is "Masse" and the other is "Maße". Proper `tolower` in German doesn't merely need a dictionary, it needs to be able to parse the meaning. – Martin Bonner supports Monica Feb 06 '19 at 09:30
  • 2
    @MartinBonner [Case mapping on Unicode is hard](https://blogs.msdn.microsoft.com/oldnewthing/20030905-00/?p=42643/) – phuclv Feb 06 '19 at 13:53
  • You are aware that `std::tolower()` is only defined for `EOF` and arguments in the `unsigned char`-range? – Deduplicator Feb 07 '19 at 12:51
  • @Deduplicator You're talking about [`std::tolower ()`](https://en.cppreference.com/w/cpp/string/byte/tolower) I think. This answer is about [`std::tolower ()`](https://en.cppreference.com/w/cpp/locale/tolower). – YSC Feb 07 '19 at 12:55
  • @YSC Where is the second Argument then? – Deduplicator Feb 07 '19 at 13:44
  • @Deduplicator With the joy to be a kid and all the other things I forgot. (fixed) – YSC Feb 07 '19 at 14:17
  • 1
    @marcelm `foo ^= 'a' ^ 'A';` would work for ASCII **and** EBCDIC. – chux - Reinstate Monica Feb 08 '19 at 15:12
34

Allow me to say that this is -- although it seems smart -- a really, really stupid hack. If someone recommends this to you in 2019, hit him. Hit him as hard as you can.
You can, of course, do it in your own software that you and nobody else uses if you know that you will never use any language but English anyway. Otherwise, no go.

The hack was arguable "OK" some 30-35 years ago when computers didn't really do much but English in ASCII, and maybe one or two major European languages. But... no longer so.

The hack works because US-Latin upper- and lowercases are exactly 0x20 apart from each other and appear in the same order, which is just one bit of difference. Which, in fact, this bit hack, toggles.

Now, the people creating code pages for Western Europe, and later the Unicode consortium, were smart enough to keep this scheme for e.g. German Umlauts and French-accented Vowels. Not so for ß which (until someone convinced the Unicode consortium in 2017, and a large Fake News print magazine wrote about it, actually convincing the Duden -- no comment on that) don't even exist as a versal (transforms to SS). Now it does exist as versal, but the two are 0x1DBF positions apart, not 0x20.

The implementors were, however, not considerate enough to keep this going. For example, if you apply your hack in some East European languages or the like (I wouldn't know about Cyrillic), you will get a nasty surprise. All those "hatchet" characters are examples of that, lowercase and uppercase are one apart. The hack thus does not work properly there.

There's much more to consider, for example, some characters do not simply transform from lower- to uppercase at all (they're replaced with different sequences), or they may change form (requiring different code points).

Do not even think about what this hack will do to stuff like Thai or Chinese (it'll just give you complete nonsense).

Saving a couple of hundred CPU cycles may have been very worthwhile 30 years ago, but nowadays, there is really no excuse for converting a string properly. There are library functions for performing this non-trivial task.
The time taken to convert several dozens kilobytes of text properly is negligible nowadays.

Dmitriy
  • 3,305
  • 7
  • 44
  • 55
Damon
  • 67,688
  • 20
  • 135
  • 185
  • 2
    I totally agree--although it is a good idea for every programmer to know why it works--might even make a good interview question.. What does this do and when should it be used :) – Bill K Feb 06 '19 at 17:58
33

It works because, as it happens, the difference between 'a' and A' in ASCII and derived encodings is 32, and 32 is also the value of the sixth bit. Flipping the 6th bit with an exclusive OR thus converts between upper and lower.

Jack Aidley
  • 19,439
  • 7
  • 43
  • 70
21

Most likely your implementation of the character set will be ASCII. If we look at the table:

enter image description here

We see that there's a difference of exactly 32 between the value of a lowercase and uppercase number. Therefore, if we do ^= 32 (which equates to toggling the 6th least significant bit), it changes between a lowercase and uppercase character.

Note that it works with all the symbols, not just the letters. It toggles a character with the respective character where the 6th bit is different, resulting in a pair of characters that is toggled back and forth between. For the letters, the respective upper/lowercase characters form such a pair. A NUL will change into Space and the other way around, and the @ toggles with the backtick. Basically any character in the first column on this chart toggles with the character one column over, and the same applies to the third and fourth columns.

I wouldn't use this hack though, as there's not guarantee that it's going to work on any system. Just use toupper and tolower instead, and queries such as isupper.

Blaze
  • 16,736
  • 2
  • 25
  • 44
  • 2
    Well, it doesn't work for all letters that have a difference of 32. Otherwise, it would work between '@' and ' '! – Matthieu Brucher Feb 05 '19 at 14:23
  • 2
    @MatthieuBrucher It is working, `32 ^ 32` is 0, not 64 – NathanOliver Feb 05 '19 at 14:28
  • @NathanOliver Yes, that's my point, it doesn't work between any two characters separated by 32, only those that have a specific pattern between them. – Matthieu Brucher Feb 05 '19 at 14:29
  • 1
    @MatthieuBrucher it toggles `@` with the backtick, not with space. Every char is part of a pair that is toggled back and forth between. Perhaps by explanation was unclear. – Blaze Feb 05 '19 at 14:31
  • @Blaze yes, the explanation is not clear enough. People that don't know about logical binary operations may not understand. + the errors in the text itself. – Matthieu Brucher Feb 05 '19 at 14:32
  • 5
    '@' and ' ' aren't "letters". Only `[a-z]` and `[A-Z]` are "letters". The rest are coincidences that follow the same rule. If someone asked you to "upper case ]", what would it be? it would still be "]" - "}" isn't the "upper case" of "]". – freedomn-m Feb 05 '19 at 16:42
  • 5
    @MatthieuBrucher: Another way to make that point is that the lower-case and upper-case alphabetic ranges don't cross a `%32` "alignment" boundary in the ASCII coding system. *This* is why bit `0x20` is the only difference between the upper/lower case versions of the same letter. If this wasn't the case, you'd need to add or subtract `0x20`, not just toggle, and for some letters there would be carry-out to flip other higher bits. (And the same operation couldn't toggle, and checking for alphabetic characters in the first place would be harder because you couldn't `|= 0x20` to force lcase.) – Peter Cordes Feb 05 '19 at 23:27
  • 1
    It is unlikely that the compiler's execution character encoding(charset) would be ASCII. It is unlikely that the locale would have ASCII as the character encoding (codeset). – Tom Blodget Feb 06 '19 at 05:11
  • 2
    +1 for reminding me of all those visits to asciitable.com to stare at that exact graphic (and the extended ASCII version!!) for the last, I dunno, 15 or 20 years? – A C Feb 06 '19 at 05:39
  • @freedomn-m *If someone asked you to "upper case ]", what would it be?* On the keyboard of my hypothetic typewriter is `}` available at the same key as `]`, I just have to shift the carriage. So yes, the operator **upper case** is defined by Remington for all characters which appear on the lower half of keys. – vitsoft Apr 08 '21 at 06:00
  • @vitsoft no, that's shift `]` -> `}`, there's no "upper case ]". It's a coincidence they're on the same keyboard and are in many cases, but not all - it's *not* an uppercase letter. What about "upper case 3" then? On a US keyboard it's `#`, on a UK keyboard it's `£` for other people it could be something else (was Euro symbol in some cases). So what's "upper case 3" - there's no such thing. The question was about converting between the two using `|=32` - which only makes sense for letters. That's why they're called "upper case *letters*". – freedomn-m Apr 08 '21 at 07:24
  • @freedomn-m *upper case 3* is the character available by pressing the key `3` together with `Shift` on a typewriter or computer keyboard. *Upper case* and *lower case* are just inaccurate legacy terms and **capital letter** and **small letter** should be used instead, as Unicode chart does. – vitsoft Apr 08 '21 at 08:06
  • @vitsoft I see, you have a misunderstanding of what an "upper case letter" is. Writing some *code* to convert from `3` to "uppercase" makes no sense if it depends on the user's keyboard. "upper case" is not the same as "shifted keyboard". shift-3 is *not* "uppercase 3". – freedomn-m Apr 08 '21 at 09:53
14

Plenty of good answers here that describe how this works, but why it works this way is to improve performance. Bitwise operations are faster than most other operations within a processor. You can quickly do a case insensitive comparison by simply not looking at the bit that determines case or change case to upper/lower simply by flipping the bit (those guys that designed the ASCII table were pretty smart).

Obviously, this isn't nearly as big of a deal today as it was back in 1960 (when work first began on ASCII) due to faster processors and Unicode, but there are still some low-cost processors that this could make a significant difference as long as you can guarantee only ASCII characters.

https://en.wikipedia.org/wiki/Bitwise_operation

On simple low-cost processors, typically, bitwise operations are substantially faster than division, several times faster than multiplication, and sometimes significantly faster than addition.

NOTE: I would recommend using standard libraries for working with strings for a number of reasons (readability, correctness, portability, etc). Only use bit flipping if you have measured performance and this is your bottleneck.

Brian
  • 37,399
  • 24
  • 94
  • 109
13

It's how ASCII works, that's all.

But in exploiting this, you are giving up portability as C++ doesn't insist on ASCII as the encoding.

This is why the functions std::toupper and std::tolower are implemented in the C++ standard library - you should use those instead.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 6
    There are protocols though, which require that ASCII is used, such as DNS. In fact, the "0x20 trick" is used by some DNS servers to insert additional entropy into a DNS query as an anti-spoofing mechanism. DNS is case insensitive, but also supposed to be case preserving, so if send a query with random case and get the same case back it's a good indication that the response hasn't been spoofed by a third party. – Alnitak Feb 05 '19 at 14:59
  • It's worth mentioning that a lot of encodings still have the same representation for the standard (not extended) ASCII characters. But still, if you're really worried about different encodings you should use the proper functions. – Captain Man Feb 05 '19 at 15:43
  • 5
    @CaptainMan: Absolutely. UTF-8 is a thing of sheer beauty. Hopefully it gets "absorbed" into the C++ standard insofar that IEEE754 has for floating point. – Bathsheba Feb 05 '19 at 15:44
10

See the second table at http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii, and following notes, reproduced below:

The Control modifier on your keyboard basically clears the top three bits of whatever character you type, leaving the bottom five and mapping it to the 0..31 range. So, for example, Ctrl-SPACE, Ctrl-@, and Ctrl-` all mean the same thing: NUL.

Very old keyboards used to do Shift just by toggling the 32 or 16 bit, depending on the key; this is why the relationship between small and capital letters in ASCII is so regular, and the relationship between numbers and symbols, and some pairs of symbols, is sort of regular if you squint at it. The ASR-33, which was an all-uppercase terminal, even let you generate some punctuation characters it didn’t have keys for by shifting the 16 bit; thus, for example, Shift-K (0x4B) became a [ (0x5B)

ASCII was designed such that the shift and ctrl keyboard keys could be implemented without much (or perhaps any for ctrl) logic - shift probably required only a few gates. It probably made at least as much sense to store the wire protocol as any other character encoding (no software conversion required).

The linked article also explains many strange hacker conventions such as And control H does a single character and is an old^H^H^H^H^H classic joke. (found here).

Community
  • 1
  • 1
Iiridayn
  • 1,747
  • 21
  • 43
  • 1
    Could implement a shift toggle for more of ASCII w/`foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20`, though this is only ASCII and therefore unwise for reasons stated in other answers. It can probably also be improved w/branch-free programming. – Iiridayn Feb 07 '19 at 19:48
  • 1
    Ah, `foo ^= 0x20 >> !(foo & 0x40)` would be simpler. Also a good example of why terse code is often considered unreadable ^_^. – Iiridayn Feb 08 '19 at 23:34
7

Xoring with 32 (00100000 in binary) sets or resets the sixth bit (from the right). This is strictly equivalent to adding or subtracting 32.

6

The lower-case and upper-case alphabetic ranges don't cross a %32 "alignment" boundary in the ASCII coding system.

This is why bit 0x20 is the only difference between the upper/lower case versions of the same letter.

If this wasn't the case, you'd need to add or subtract 0x20, not just toggle, and for some letters there would be carry-out to flip other higher bits. (And there wouldn't be a single operation that could toggle, and checking for alphabetic characters in the first place would be harder because you couldn't |= 0x20 to force lcase.)


Related ASCII-only tricks: you can check for an alphabetic ASCII character by forcing lowercase with c |= 0x20 and then checking if (unsigned) c - 'a' <= ('z'-'a'). So just 3 operations: OR + SUB + CMP against a constant 25. Of course, compilers know how to optimize (c>='a' && c<='z') into asm like this for you, so at most you should do the c|=0x20 part yourself. It's rather inconvenient to do all the necessary casting yourself, especially to work around default integer promotions to signed int.

unsigned char lcase = y|0x20;
if (lcase - 'a' <= (unsigned)('z'-'a')) {   // lcase-'a' will wrap for characters below 'a'
    // c is alphabetic ASCII
}
// else it's not

Or to put it another way:

 unsigned char lcase = y|0x20;
 unsigned char alphabet_idx = lcase - 'a';   // 0-index position in the alphabet
 bool alpha = alphabet_idx <= (unsigned)('z'-'a');

See also Convert a String In C++ To Upper Case (SIMD string toupper for ASCII only, masking the operand for XOR using that check.)

And also How to access a char array and change lower case letters to upper case, and vice versa (C with SIMD intrinsics, and scalar x86 asm case-flip for alphabetic ASCII characters, leaving others unmodified.)


These tricks are mostly only useful if hand-optimizing some text-processing with SIMD (e.g. SSE2 or NEON), after checking that none of the chars in a vector have their high bit set. (And thus none of the bytes are part of a multi-byte UTF-8 encoding for a single character, which might have different upper/lower-case inverses). If you find any, you can fall back to scalar for this chunk of 16 bytes, or for the rest of the string.

There are even some locales where toupper() or tolower() on some characters in the ASCII range produce characters outside that range, notably Turkish where I ↔ ı and İ ↔ i. In those locales, you'd need a more sophisticated check, or probably not trying to use this optimization at all.


But in some cases, you're allowed to assume ASCII instead of UTF-8, e.g. Unix utilities with LANG=C (the POSIX locale), not en_CA.UTF-8 or whatever.

But if you can verify it's safe, you can toupper medium-length strings much faster than calling toupper() in a loop (like 5x), and last I tested with Boost 1.58, much much faster than boost::to_upper_copy<char*, std::string>() which does a stupid dynamic_cast for every character.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847