4

I'd like to be able to copy and paste, into MMA, numbers that have a standard USD currency format, i.e., that include dollar signs and comma separators, and get the output in the same format. I'd also like this to be done automatically, without the need for a formula, which I assume means making use of one or more $Pre-type statements.

For instance, I'd like to be able to paste the following into an input cell:

$3,342.56 + $1000 + 100.5 + $45.35*1.57

and get:

$4,514.26

[The following is an edit from earlier version of this post, which used $Pre.] I can create a formula that provides the output in currency format, except that it doesn't pad to two decimals if the input number ends in the tenths place:

$PrePrint = 
    AccountingForm[N[#, 30], {Infinity, 2}, DigitBlock -> 3, 
    NumberSigns -> {"-$", "$"}] &;

4514.26 358.3

$4,514.26

$358.3

But, on the input side, I haven't figured out how to create a function that can strip dollar signs and commas from an expression. I can only get it to work if I first manually convert the expression to a string by wrapping it in quotes:

$Pre = ToExpression@StringReplace[ToString[#], {"," -> "", "$" -> ""}] &;
$PrePrint = 
  AccountingForm[N[#, 30], {Infinity, 2}, DigitBlock -> 3, 
  NumberSigns -> {"-$", "$"}] &;

"$3,342.56 + $1000 + 100.5 + $45.35*1.57" % + 1000 %% + "$1,000"

$4,514.26

$5,514.26

$5,514.26

In fact, if I have to wrap the input in quotes anyways, I can accomplish the above using only $PrePrint; this gives the same output as the above code:

$PrePrint = 
    AccountingForm[
    N[ToExpression@StringReplace[ToString@#, {"," -> "", "$" -> ""}],
    30], {Infinity, 2}, DigitBlock -> 3, 
    NumberSigns -> {"-$", "$"}] &;

An explanation of what's motivating this

I get emails in which we're going back and forth discussing funding for grant proposals, and I'll need to run some calculations and reply. To do this, I'll cut and paste the numbers in the email into Mathematica, manually strip out the dollar signs and commas, do the calculation, paste the result back into my reply email, and add the dollars and commas back. Since sometimes there are many of these, it would be a nice convenience if I could avoid the two manual steps, i.e., avoid having to strip out the dollar signs and commas before doing the calculation, and having to add them back after pasting the result in the email.

Ultimately, when I enter $3,342.56 + $1000 + 100.5 + $45.35*1.57, I'd like to combine the answer to this with other code I already have such that MMA outputs:

$3,342.56 + $1000 + 100.5 + $45.35*1.57 = $4,514.26.

Then I can just paste the whole thing back into my email.

theorist
  • 3,633
  • 1
  • 15
  • 27
  • Might you be able to wrap the input in quotes so it is read as a string using $Pre? Then you can strip the $ and the commas with your initial simple function. – Nicholas G May 09 '21 at 12:48
  • Do you mean that I manually add the quotes mysef after the copying and pasting? That's an extra typing step I'm hoping to avoid. I want to be able to just paste the input into a cell, hit shift-enter, and get a properly-formatted answer. – theorist May 09 '21 at 16:51
  • I cannot make $Pre work. Even $Pre=ToString /@ {#} &, which might have worked by making the input into a list and mapping ToString over all its elements does not work. It converts $2 in input alone into a list with one element but the comma that follows it makes Ma expect a list and since the list is not in braces, Ma thinks it is incomplete. – Nicholas G May 09 '21 at 21:13
  • One big issue is that many expressions of this form are syntactically invalid. None of what you've written works for me and corrupting $Pre like that (so that it is impossible to revert) seems like a terrible idea. I'm struggling to see why this is better than just defining some unit symbol like $USD so that you can do like 100 $USD and then you simply use UpValues on $USD to convert 100 $USD into Quantity[100, "Dollars"] or something. The latter seems safer, easier, and less buggy – b3m2a1 May 09 '21 at 22:18
  • @Nicholas G Thanks for letting me know that $Pre is right command to use. With it, I was at least able to get MMA to automatically give me my output in the desired form, with $Pre = StringJoin[ToString["$"], ToString[AccountingForm[#, DigitBlock -> 3]]] &; (I've updated the first part of my question accordingly). – theorist May 09 '21 at 22:19
  • @b3m2a1 Can you explain what you mean "impossible to revert"? If I enter $Pre=. it returns to normal behavior. However, when I do this, it returns $Null--what problem does that indicate? – theorist May 09 '21 at 22:21
  • @theorist if you combine your $Pre with pretty much any type of $PreRead you'll only ever get $$Failed – b3m2a1 May 09 '21 at 22:22
  • @b3m2a1 It works fine for me with this $PreRead + $PrePrint code, which can be found right after the "Update" heading:

    https://mathematica.stackexchange.com/questions/138800/how-do-i-alter-this-preread-preprint-statement-so-it-can-be-selectively-de?rq=1

    – theorist May 09 '21 at 22:36

1 Answers1

3

Stylesheet Approach

Easier than messing with $Pre is to never let the string get converted to boxes in the first place. We can do that by making a special CellStyle that inherits from "Text" but is Evaluatable and has as its CellEvaluationFunction the necessary code to interpret these inputs.

By evaluating this in a Notebook you can make such a CellStyle appear and be bound to Command-8

SetOptions[
 EvaluationNotebook[],
 StyleDefinitions ->
  Notebook[{
    Cell[StyleData[StyleDefinitions -> "Default.nb"]],
    Cell[StyleData["Code"], MenuCommandKey -> None],
    Cell[
     StyleData["USDInput", StyleDefinitions -> StyleData["Text"]],
     MenuCommandKey -> "8",
     FontFamily -> "Source Code Pro",
     Evaluatable -> True,
     CellEvaluationFunction -> Function[
       canonicalizeUSD[string_] :=
        StringReplace[
         StringDelete[string, ","],
         {
          "$" ~~ n : NumberString :> "Quantity[" <> n <> ", \"USDollars\"]"
          }
         ];
       calcUSD[string_] :=
        canonicalizeUSD[string] // ToExpression;
       calcUSDSloppy[string_] :=
        Quantity[
         calcUSD[string] /. q_Quantity :> QuantityMagnitude[q],
         "USDollars"
         ];
       formatUSDNumber[q_] :=
        Quantity[
         NumberForm[q // QuantityMagnitude,
          {RealDigits[QuantityMagnitude@q][[2]] + 2, 2},
          DigitBlock -> 3,  NumberSeparator -> ","],
         "USDollars"
         ];
       formatUSDNumber@calcUSDSloppy[#]
       ]
     ]
    }
   ]
 ]

This then works like a free-form input cell, not string quotes necessary

enter image description here

Easiest Form

Here's my suggestion from the comments. Notice how easy and safe it is

$USD /: Times[n_, $USD] := Quantity[n, "USDollars"];
$USD /: Times[$USD, n_] := Quantity[n, "USDollars"];
3342.56 $USD + 1000 $USD + 100.5 $USD + 45.35 $USD*1.57

Quantity[4514.2595, "USDollars"]

It...just works. And it uses all of the built-in Quantity machinery so you don't have to do any work yourself.

String-Based Approach

If you're copy-pasting these strings, I think this is the cleanest approach. First you use proper string-ops on the strings themselves to canonicalize the string and then you let Mathematica do the rest:

canonicalizeUSD[string_] :=
  StringReplace[
   StringDelete[string, ","],
   {
    "$" ~~ n : NumberString :> "Quantity[" <> n <> ", \"USDollars\"]"
    }
   ];
calcUSD[string_] :=
 canonicalizeUSD[string] // ToExpression

This is still easy to copy, paste and run

"$3,342.56 + $1000 + $100.5 + $45.35*1.57" // calcUSD
(* $ 4514.26  (copied using Command-Shift-C which is the short-cut for copy as plaintext)*) 

If you don't have the proper units on the strings obviously that will break things

"$3,342.56 + 1000 + 100.5 + $45.35*1.57" // calcUSD

1100.5 + Quantity[3413.7595, "USDollars"]

But you can fix this like by adding a special case for less-pristine inputs

calcUSDSloppy[string_] :=
 Quantity[
  calcUSD[string] /. q_Quantity :> QuantityMagnitude[q],
  "USDollars"
  ]

"$3,342.56 + 1000 + 100.5 + $45.35*1.57" // calcUSDSloppy

Quantity[4514.2595, "USDollars"]

Or you can strip the Quantity altogether at the end with QuantityMagnitude if all you want is the value.

If you want specific formatting, that's not hard to get

formatUSDNumber[q_] :=
 Quantity[
  NumberForm[q // QuantityMagnitude,
   {RealDigits[QuantityMagnitude@q][[2]] + 2, 2},
   DigitBlock -> 3,  NumberSeparator -> ","],
  "USDollars"
  ]

"$3,342.56 + 1000 + 100.5 + $45.35*1.57" // calcUSDSloppy // formatUSDNumber

(* $ 4,514.26 *)

Finally, worth mentioning that if you have mixed-units (say both Canadian and US dollars) the previous approach is cleaner, even if it means you need to do a minor amount of preprocessing of the input.

Free-Form Input Approach

You can also use a free-form cell which basically asks W|A to do it's free-form input cleaning. You can get one of those with = at the start of an "Input" cell

enter image description here

b3m2a1
  • 46,870
  • 3
  • 92
  • 239
  • This is going in the opposite direction of what I want to a achieve, which is to automate things. With your solution, I have to manually paste $USD's after each dollar figure. It's quicker to instead just copy the normal numerical output and manually add a dollar sign and a comma after I paste it back into my email (which is what I'm doing now). Maybe I need to edit my post to add an explanation of my motivation for doing this, so folks can better understand why I requested exactly what I did. – theorist May 09 '21 at 22:43
  • Then I'd suggest doing this as a string-processing thing, like "<paste your stuff>" // calcUSD where calcUSD just canonicalizes the input. That's another pretty trivial operation that doesn't require Mathematica to read syntactically-invalid statements and attempt to not mess them up before you run them through your code... Like $Pre can be nice, but it's also more effort than it's worth if you're basically trying to rewrite how Mathematica reads statements – b3m2a1 May 09 '21 at 22:46
  • Note: I was not the one that downvoted your answer! Also, I just added an explanatory statement at the end that might help clarify things. Not sure if I'm following your comment--could you provide some sample code? – theorist May 09 '21 at 22:55
  • @theorist don't worry, I don't care about the downvote. I added a note on what a plain string approach would look like. In my mind it is easier to just work with a string if you're supplying a string, rather than taking a string to a bunch of boxes in the FE to a processed-version of that (again in the FE) and finally to a string that you then process in the kernel. Another thought: have you tried the free-form input cells? That might be even easier. You can make one by typing = at the start of an Input cell. – b3m2a1 May 09 '21 at 23:25
  • That's pretty close--I'd still have to wrap the whole thing in quotes, which woudn't be too bad if I didn't have to do any other cleanup. So: How would I get it to display to two decimal places and show the commas (e.g., $1,435.40 instead of $1435.4), and implement it in $Pre? – theorist May 09 '21 at 23:42
  • 1
    @theorist I don't want to have to figure out how to make $Pre work, since $Pre will likely be a nightmare for syntactically-invalid stuff I think. But the commas are easy. I added a function to get the formatting clean. – b3m2a1 May 09 '21 at 23:49
  • @theorist I added an approach that circumvents the issues with $Pre but still allows you to copy-paste-evaluate. This is a much safer, more general approach than $Pre in 90% of cases anyway – b3m2a1 May 10 '21 at 00:05
  • Understood.Thanks. Just curious: Given that it's outputting a Quantity as USD, why can't I add another USD Quantity to it? E.g., "$3,342.56 + 1000 + 100.5 + $45.35*1.57" // calcUSDSloppy // formatUSDNumber % + Quantity[1000, "USDollars"]. I'll add it's striking that calculations that are so straighforward in Excel (inputting numbers in mixed currency and non-currency formats, and getting output in a currency format) is so difficult in Mathematica.Plus, w/ Excel, it's easy to separate the formatting (dollar signs, etc) from the interal meaning (a number). Diff. tools for diff. tasks... – theorist May 10 '21 at 00:10
  • @theorist because you explicitly formatted the number? A rule of thumb in all things Mathematica is that only outputs should be formatted. The reason Excel has better support for this is that Excel is designed for the use case where you have messy data that you're working with by hand. Mathematica is much more flexible and general and therefore can't favor one use case over another. What you want here would break something else in a different piece of the code base. – b3m2a1 May 10 '21 at 00:12
  • What about instead using AccountingForm[N[#, 30], {Infinity, 2}, DigitBlock -> 3, NumberSigns -> {"-$", "$"}] &; to format the output? It appends the dollar signs and commas, yet leaves the internal meaning as a number. The only thing it doesn't do is always express the result to 2 decimal places. – theorist May 10 '21 at 00:39
  • Also, thanks for that cell style approach, but it's not working for me. E.g., when I enter $123.45+$3,555.33+$100.54 and press cmd-8 and then shift-enter, it returns $1775.34 instead of the correct value ($4,779.32). And when I try copying and pasting that number into an email, I get the whole underlying expression instead of the number itself, i.e.: Quantity[\!\( TagBox[ InterpretationBox[ StyleBox["\<\"1,775.34\"\>", ShowStringCharacters->False], 1775.3400000000001\, AutoDelete->True], NumberForm[#, {6, 2}, DigitBlock -> 3, NumberSeparator -> ","]& ]), "USDollars"]` – theorist May 10 '21 at 01:37
  • @theorist works fine for me. Did you add a space somewhere by accident? Also, as I pointed out early, use Command-Shift-C to copy text (or probably Ctrl-Shift-C) on Windows. That's just general Mathematica knowledge where if you want to copy exactly what's on the screen you use copy as plaintext – b3m2a1 May 10 '21 at 01:39
  • I tried the follwing three times, all with the same result: Copy and paste the style sheet from your answer into a new notebook with a new kernel, evaluated it, pasted in the numbers, checked for extra spaces, and then did cmd-8 + shift-enter. Same result every time. The reason it's miscalculating is that when I select the cell with the numbers, and press cmd-8, it adds a space after each comma. Thus what gets added are only those parts of the numbers after the comma. E.g., $1,300+$1,300 gives $600. Could you try checking by copying what you posted in your answer into a new notebook? – theorist May 10 '21 at 01:53
  • @theorist If I copy-paste the code block, evaluate it with the kernel reset (just in case), make a cell with Command-8, copy-paste your $1,300+$1,300, and shift-enter to eval, I get $2600. Are you sure your input is clean? Alternately maybe the FE is doing something weird based on the version. What version are you on? – b3m2a1 May 10 '21 at 01:57
  • 1
    I see the problem--as I mentioned, I was first pasting in the numbers, and then pressing cmd-8; but from your last comment I realize this needs to be done in reverse order. It works now. – theorist May 10 '21 at 02:10
  • 1
    @theorist and that illustrates why the $Pre in an "Input" cell approach would be such a pain. Mathematica inserts all sorts of formatting in an "Input" cell as it converts the string to boxes and then you'd need to basically reverse that entire process. This trick is just to keep everything as "Text" input the whole time – b3m2a1 May 10 '21 at 02:11