Last active
April 29, 2024 06:12
-
-
Save earthdiver/2ccc2991db9b05b11b9080110becd445 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Numerics | |
<# | |
.SYNOPSIS | |
A tool that enables the use of the PowerShell console as a scientific calculator. | |
.DESCRIPTION | |
Accepts standard mathematical expressions as input and outputs the results on the console by | |
internally converting the expressions into forms that PowerShell can parse. | |
If no expression is specified as an argument, a read-eval-print-loop (REPL) session will start. | |
Intended for interactive use. | |
.PARAMETER Expr | |
Specifies a mathematical expression to evaluate (optional). | |
It is recommended to use this in combination with the stop-parsing token (--%). | |
.INPUTS | |
Expressions can be passed as the first argument or specified at REPL prompts. This function | |
does not accept inputs from a pipeline. | |
.OUTPUTS | |
This function does not return a value. Outputs will be redirected to the host device. | |
.NOTES | |
Commands : exit, quit, verbose on, verbose off, ? (the last invoked expression), err (the last error) | |
Constants: e (=2.71828...), pi (=3.14159...) | |
img. unit: i (=[Comprex]::new(0,1)) | |
Functions: Abs, Acos, Acosh, Asin, Asinh, Atan, Atan2, Atanh, Ceiling, Cos, Cosh, Exp, Floor, | |
Log, Log10, Log2, Max, Min, Pow, Round, Sign, Sin, Sinh, Sqrt, Tan, Tanh, Truncate | |
UserFunc.: f1, f2, f3, g1, g2, g3, h1, h2, h3 | |
Operators: ! (factorial), ^/** (powers), % (remainder), *, +, -, /, | |
and other arithmetic operators that start with '-' | |
Variables: $result (the result of the previous expression; 'ans' can be used instead.) | |
Remarks : - Two constants, 'pi' and 'e' (the base of the natural logarithms), are available. | |
- There is no need to prefix math functions such as 'Abs' with [Math]:: or [Bigint]::, | |
as [Math]:: or [Math_]:: are automatically added internally. Methods with the same | |
name that call math functions contained in the System.Math, System.Numerics.BigInteger, | |
and System.Numerics.Complex classes are overloaded in the Math_ class. | |
- The power operator can be written as '^' or '**', and the factorial operator as '!'. | |
- PowerShell's arithmetic operators (starting with a minus sign, such as '-band') | |
can be used. | |
- PowerShell functions can be defined and used. For example, you can define a function | |
as 'function f($x,$y){$x+$y}', and call it as 'f 3 4'. You can also write a function | |
definition as 'f($x,$y):=$x+$y'. If operations are performed on the result of a | |
user-defined function, it must be enclosed in parentheses (e.g., '2 * pi * (f 3 4)'). | |
- The following 9 functions can be used as usual, because they are rewritten as | |
class methods internally: f1, g1, h1 (functions with one argument), f2, g2, h2 | |
(functions with two arguments), and f3, g3, h3 (functions with three arguments). | |
For example, you can define a function as 'f2($x,$y):=$x^2+$y^2' and then call it | |
as 'f2(3,4)'. | |
- PowerShell variables (beginning with $) can be defined and used. Only alphanumeric | |
characters and underscores (_) can be used in user-defined variable names. Using other | |
characters in variable names may cause incorrect behavior. | |
- The result of the previous expression is stored in the $result variable and can be | |
referenced in the next expression. 'ans' can also be used to specify the last result. | |
- If you enter 'verbose on', subsequent sessions will output the internally converted | |
formulas. In addition, additional error messages are displayed when a syntax error | |
occurs. If you enter 'verbose off', the output will stop. Also, the last invoked | |
expression can be displayed using the '?' command. | |
- Suffixes for numeric literals such as 'd' (Decimal), 'l' (Long), and 'n' (BigInteger | |
*in PowerShell 7.0 or later) can be specified (e.g. '0.1d'). If you want to | |
explicitly specify the 'Double' type, you can use casting such as '[Double]0.1'. | |
- You can represent hexadecimal numbers in the format of '0xFFFFFFFF'. In addition, | |
in PowerShell 7.0 or later, you can represent binary numbers in the format of | |
'0b11111111' and specify a suffix 'u' to indicate unsigned. | |
- Instead of creating an object like [Numerics.Complex]::new(1,-2), complex numbers | |
can also be written such as '1 - 2i'. | |
- If a real number is specified for an operation targeting integers, the number will | |
be rounded to the nearest integer before the operation. | |
- You can execute many of the PowerShell cmdlets. | |
Author : earthdiver1 | |
Version: 1.11 | |
Licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. | |
.EXAMPLE | |
PS> pscalc --% sqrt(2) | |
=> invoked expr.: [Math_]::sqrt(2) | |
1.4142135623731 | |
PS> Invoke-MathExpression | |
Calc> (1 + 2/3) * 4 | |
6.66666666666667 | |
Calc> sqrt( 5 ) | |
2.23606797749979 | |
Calc> 5^(2^-1) | |
2.23606797749979 | |
Calc> $a = pi/4 | |
Calc> $a | |
0.785398163397448 | |
Calc> sin( $a ) | |
0.707106781186547 | |
Calc> (sin( $a ))^2 + (cos( $a ))^2 | |
1 | |
Calc> tan( pi/4 ) | |
1 | |
Calc> atan( 1 ) | |
0.785398163397448 | |
Calc> ans * 4 | |
3.14159265358979 | |
Calc> 10! | |
3628800 | |
Calc> ? | |
=> last invoked exp. : [Math_]::Fac(10) | |
Calc> f1( $n ) := switch( $n ){ 0{1} 1{1} default{ $n * f1($n - 1) } } | |
Calc> f1( 10 ) | |
3628800 | |
Calc> verbose on | |
Calc> f2( $x, $y ) := $x * $x + $y * $y | |
=> invoked expression: function f2( $x, $y ) { $x * $x + $y * $y } | |
Calc> sqrt( f2( 3 , 4 ) ) | |
=> invoked expression: [Math_]::sqrt( [Math_]::f2( 3 , 4 ) ) | |
5 | |
Calc> verbose off | |
Calc> f1( $x ) := f2( sin( $x ), cos( $x ) ) | |
Calc> f1( $a ) | |
1 | |
Calc> 2^2^2^2 | |
Syntax error | |
Calc> ((2^2)^2)^2 | |
256 | |
Calc> 2^(2^(2^2)) | |
65536 | |
Calc> 2^256 | |
1.15792089237316E+77 | |
Calc> ([bigint]2)^256 # can be written as '2n^256' on PowerShell 7.0 | |
115792089237316195423570985008687907853269984665640564039457584007913129639936 | |
Calc> (1-2i)^3 | |
-11 + 2i | |
Calc> exp(i) | |
0.54030230586814 + 0.841470984807897i | |
Calc> cos(1) + sin(1) * i | |
0.54030230586814 + 0.841470984807897i | |
#> | |
function Invoke-MathExpression { | |
[Alias('psCalc')] | |
param( [Parameter(Position=0,ValueFromRemainingArguments)][string]$Expr ) | |
class Math_ { | |
static [bigint] Abs( [bigint]$x ) { return [bigint]::Abs( $x ) } | |
static [object] Abs( [object]$x ) { return [Math]::Abs( $x ) } | |
static [double] Abs( [complex]$x ) { return [complex]::Abs( $x ) } | |
static [double] Acos( [double]$x ) { return [Math]::Acos( $x ) } | |
static [complex] Acos( [complex]$x ) { return [complex]::Acos( $x ) } | |
static [double] Asin( [double]$x ) { return [Math]::Asin( $x ) } | |
static [complex] Asin( [complex]$x ) { return [complex]::Asin( $x ) } | |
static [double] Atan( [double]$x ) { return [Math]::Atan( $x ) } | |
static [complex] Atan( [complex]$x ) { return [complex]::Atan( $x ) } | |
static [double] Cos( [double]$x ) { return [Math]::Cos( $x ) } | |
static [complex] Cos( [complex]$x ) { return [complex]::Cos( $x ) } | |
static [double] Cosh( [double]$x ) { return [Math]::Cosh( $x ) } | |
static [complex] Cosh( [complex]$x ) { return [complex]::Cosh( $x ) } | |
static [double] Exp( [double]$x ) { return [Math]::Exp( $x ) } | |
static [complex] Exp( [complex]$x ) { return [complex]::Exp( $x ) } | |
static [double] Log( [bigint]$x ) { return [bigint]::Log( $x ) } | |
static [double] Log( [double]$x ) { return [Math]::Log( $x ) } | |
static [complex] Log( [complex]$x ) { return [complex]::Log( $x ) } | |
static [double] Log( [bigint]$x, [double]$y ) { return [bigint]::Log( $x, $y ) } | |
static [double] Log( [double]$x, [double]$y ) { return [Math]::Log( $x, $y ) } | |
static [complex] Log( [complex]$x, [double]$y ) { return [complex]::Log( $x, $y ) } | |
static [double] Log10( [bigint]$x ) { return [bigint]::Log10( $x ) } | |
static [double] Log10( [double]$x ) { return [Math]::Log10( $x ) } | |
static [complex] Log10( [complex]$x ) { return [complex]::Log10( $x ) } | |
static [bigint] Log2( [bigint]$x ) { return [bigint]::Log2( $x ) } | |
static [double] Log2( [double]$x ) { return [Math]::Log2( $x ) } | |
static [bigint] Max( [bigint]$x, [bigint]$y ) { return [bigint]::Max( $x, $y ) } | |
static [object] Max( [object]$x, [object]$y ) { return [Math]::Max( $x, $y ) } | |
static [bigint] Min( [bigint]$x, [bigint]$y ) { return [bigint]::Min( $x, $y ) } | |
static [object] Min( [object]$x, [object]$y ) { return [Math]::Min( $x, $y ) } | |
static [bigint] Pow( [bigint]$x, [int]$y ) { return [bigint]::Pow( $x, $y ) } | |
static [double] Pow( [double]$x, [double]$y ) { return [Math]::Pow( $x, $y ) } | |
# static [complex] Pow( [complex]$x, [double]$y ) { return [complex]::Pow( $x, $y ) } | |
static [complex] Pow( [complex]$x, [double]$y ) { | |
if ( $y -eq [Math]::Truncate($y) -and [Math]::Abs($y) -le 1023.0 ) { | |
$n = [int]$y | |
if ( $n -eq 0 ) { return [complex]::One } | |
if ( $n -gt 0 ) { | |
$p = [complex]::One | |
while ( $n -gt 0 ) { | |
if ( $n -band 1 ) { $p *= $x } | |
$x *= $x | |
$n = $n -shr 1 | |
} | |
return $p | |
} else { | |
return 1.0 / [Math_]::Pow( $x, -$y ) | |
} | |
} | |
return [complex]::Pow( $x, $y ) | |
} | |
static [double] Sin( [double]$x ) { return [Math]::Sin( $x ) } | |
static [complex] Sin( [complex]$x ) { return [complex]::Sin( $x ) } | |
static [double] Sinh( [double]$x ) { return [Math]::Sinh( $x ) } | |
static [complex] Sinh( [complex]$x ) { return [complex]::Sinh( $x ) } | |
# static [double] Sqrt( [double]$x ) { return [Math]::Sqrt( $x ) } | |
static [object] Sqrt( [double]$x ) { | |
if ( $x -lt 0 ) { return [complex]::new( 0, [Math]::Sqrt( -$x ) ) } | |
return [Math]::Sqrt( $x ) | |
} | |
static [complex] Sqrt( [complex]$x ) { return [complex]::Sqrt( $x ) } | |
static [double] Tan( [double]$x ) { return [Math]::Tan( $x ) } | |
static [complex] Tan( [complex]$x ) { return [complex]::Tan( $x ) } | |
static [double] Tanh( [double]$x ) { return [Math]::Tanh( $x ) } | |
static [complex] Tanh( [complex]$x ) { return [complex]::Tanh( $x ) } | |
static [bigint] Fac( [bigint]$n ) { | |
if ( $n -lt [bigint]::Zero ) { throw "Negative number specified for factorial." } | |
$maxdigits = 100000. | |
$x = [double]$n | |
if ( [Math]::Abs( 0.5 * [Math]::Log10( 2 * [Math]::PI * $x ) + | |
$x * [Math]::Log10( $x / [Math]::E ) ) -gt $maxdigits ) { | |
throw "Number of digits exceeded the upper limit(=$maxdigits)." | |
} | |
$f = [bigint]::One | |
while ( $n -gt [bigint]::One ) { | |
$f *= $n | |
$n -= [bigint]::One | |
} | |
return $f | |
} | |
static [object] Fac( [object]$x ) { | |
$n = [long]$x | |
if ( $n -lt 0 ) { throw "Negative number specified for factorial." } | |
if ( $n -gt 170 ) { | |
return [double]::PositiveInfinity | |
} elseif ( $n -gt 27 ) { | |
return 1..$n | & { begin{ $f = 1.0 } process{ $f *= [double]$_ } end{ $f } } | |
} elseif ( $n -gt 1 ) { | |
return 1..$n | & { begin{ $f = 1.d } process{ $f *= [decimal]$_ } end{ $f } } | |
} else { | |
return 1d | |
} | |
} | |
static [object] f1( [object]$x ) { return (f1 $x) } | |
static [object] g1( [object]$x ) { return (g1 $x) } | |
static [object] h1( [object]$x ) { return (h1 $x) } | |
static [object] f2( [object]$x, [object]$y ) { return (f2 $x $y) } | |
static [object] g2( [object]$x, [object]$y ) { return (g2 $x $y) } | |
static [object] h2( [object]$x, [object]$y ) { return (h2 $x $y) } | |
static [object] f3( [object]$x, [object]$y, [object]$z ) { return (f3 $x $y $z) } | |
static [object] g3( [object]$x, [object]$y, [object]$z ) { return (g3 $x $y $z) } | |
static [object] h3( [object]$x, [object]$y, [object]$z ) { return (h3 $x $y $z) } | |
} | |
$params = @{ | |
TypeName = 'System.Numerics.Complex' | |
MemberType = 'ScriptMethod' | |
MemberName = 'ToString2' | |
Value = { | |
param( [string]$format = "" ) | |
if ( $this.Real -ne 0.0 ) { | |
$cmplx = "$($this.Real.ToString($format))" | |
if ( $this.Imaginary -gt 0.0 ) { $cmplx += " + $( $this.Imaginary.ToString($format))i" } | |
if ( $this.Imaginary -lt 0.0 ) { $cmplx += " - $(-$this.Imaginary.ToString($format))i" } | |
} else { | |
$cmplx = "0" | |
if ( $this.Imaginary -ne 0.0 ) { $cmplx = "$($this.Imaginary.ToString($format))i" } | |
} | |
return $cmplx -replace '(?<![.0-9])1i$','i' | |
} | |
} | |
Update-TypeData @params -Force | |
$options = [Text.RegularExpressions.RegexOptions]'ECMAScript, IgnoreCase' | |
$iunit = [complex]::ImaginaryOne | |
$e = [Math]::E | |
$pi = [Math]::PI | |
$binhex = '\b(?:0b[01]+|0x[0-9a-f]+)[inu]?\b' | |
$integer = '(?<![\w.])[0-9]+[iln]?(?![\w.])' | |
$decimal = '(?<![\w.])(?:[0-9]+(?:\.[0-9]*|(?=[de]))|\.[0-9]+)(?:e[+-]?[0-9]+)?[din]?(?![\w.])' | |
$number = "(?:$binhex|$integer|$decimal)" | |
$usrvar = '\$[0-9_a-z]+(?:\.[0-9_a-z]+|\[[^\]]+\])?(?![\w.[])' | |
#$strwip = '\((?>(?:[^()]+|(?<o>\()|(?<-o>\)))*)(?(o)(?!))\)' # string within parenthesis (similar to '(?<p>\((?>(?:[^()]+|(?&p))*)\))' or '(?<p>\((?>[^()]*(?:(?&p)[^()]*)*)\))' with PCRE) | |
$strwip = '\((?>[^()]*(?:(?:(?<o>\()[^()]*)+(?:(?<-o>\))[^()]*)+)*)(?(o)(?!))\)' # string within parenthesis | |
$operand = "(?:$number|$usrvar|\B$strwip)" | |
$mathfn1 = "(?<!]::)\b((?:Acosh|Asinh|Atan2|Atanh|Ceiling|Floor|Round|Sign|Truncate)$strwip)" | |
$mathfn2 = "(?<!]::)\b((?:Abs|Acos|Asin|Atan|Cos|Cosh|Exp|Log|Log10|Log2|Max|Min|Pow|Sin|Sinh|Sqrt|Tan|Tanh)$strwip)" | |
$factor = "(?<!(?:\*\*|\^)\s*)($operand)\s*!(?!\s*(?:\*\*|[!^]))" | |
$powers = "(?<!(?:\*\*|\^)\s*)($operand)\s*(?:\*\*|\^)\s*([+-]?\s*$operand)(?!\s*(?:\*\*|[!^]))" | |
$usrfdef = '^\s*([a-z][0-9_a-z]*\s*(?:\([^)]*\))?)\s*:=\s*(.+)\s*$' | |
$usrfn = "(?<!(?:\bfunction\s+|]::))\b([fgh][123]$strwip)" | |
$vars = @('$e','$pi','$binhex','$cmplx','$decimal','$expression','$factor','$integer','$iunit', | |
'$lastex','$mathfn1','$mathfn2','$number','$operand','$options','$powers','$strwip', | |
'$usrfdef','$usrfn','$usrvar','$verbose','$var','$vars') | |
$eval = { | |
$expression = [regex]::Replace($expression, '\bans\b', '$$result', $options) | |
$expression = [regex]::Replace($expression, '\bi\b', '$$iunit', $options) | |
$expression = [regex]::Replace($expression, "($number)(?<=i)", "(`$1`0*`$`$iunit)", $options).Replace("i`0",'') | |
$expression = [regex]::Replace($expression, '(?<!]::)\bPi\b', '$$pi',$options) | |
$expression = [regex]::Replace($expression, '(?<!]::)\bE\b', '$$e' ,$options) | |
$expression = [regex]::Replace($expression, $usrfdef, 'function $1 { $2 }', $options) | |
while ( [regex]::IsMatch($expression, $mathfn1, $options) ) { | |
$expression = [regex]::Replace($expression, $mathfn1, '[Math]::$1', $options) | |
} | |
while ( [regex]::IsMatch($expression, $mathfn2, $options) ) { | |
$expression = [regex]::Replace($expression, $mathfn2, '[Math_]::$1', $options) | |
} | |
while ( [regex]::IsMatch($expression, $usrfn, $options) ) { | |
$expression = [regex]::Replace($expression, $usrfn, '[Math_]::$1', $options) | |
} | |
while ( [regex]::IsMatch($expression, $factor, $options) ) { | |
$expression = [regex]::Replace($expression, $factor, '[Math_]::Fac($1)', $options) | |
} | |
while ( [regex]::IsMatch($expression, $powers, $options) ) { | |
$expression = [regex]::Replace($expression, $powers, '[Math_]::Pow($1,$2)', $options) | |
} | |
$expression = $expression.Replace('[Math_]::Pow($e,', '[Math_]::Exp(') | |
if ( $verbose ) { Write-Host "=> invoked expr.: $expression" } | |
try { | |
$lastex = $expression | |
( $result = Invoke-Expression $expression ) | ForEach-Object { | |
if ( $_ -and $_.GetType().Name -eq "Complex" ) { $_.ToString2() } else { $_ } | |
} | Out-Host | |
} catch { | |
if ( $_.Exception.WasThrownFromThrowStatement ) { | |
Write-Host $_.Exception.Message -ForegroundColor Red | |
} else { | |
Write-Host "Syntax error" -ForegroundColor Red | |
if ( $verbose ) { | |
Write-Host $_.Exception.Message -BackgroundColor Black -ForegroundColor Yellow | |
} | |
} | |
} | |
} | |
$expression = $Expr.Replace("--% ","") -replace '^-E(x(pr?)?)? ' | |
$verbose = $expression -ne "" | |
if ( $expression ) { | |
. $eval | |
} else { | |
$lastex = "" | |
$result = "" | |
:loop | |
while ( $true ) { | |
Write-Host "Calc> " -NoNewLine | |
$expression = ( $Host.UI.ReadLine() -replace '#.*$' ).Trim() | |
foreach ( $var in $vars ) { | |
if ( $expression -match "\$var\b" ) { | |
Write-Host "Invalid variable name. '$var' is reserved for the program." -ForegroundColor Red | |
continue loop | |
} | |
} | |
switch -Regex ( $expression ) { | |
'^(exit|quit)$' { break loop } | |
'^$' { continue loop } | |
'^verbose\s+on$' { $verbose = $true ; continue loop } | |
'^verbose\s+off$' { $verbose = $false; continue loop } | |
'^\?$' { Write-Host "=> prev. expr. : $lastex" } | |
'^err$' { Write-Host "=> last error : $($global:Error[0].Exception.Message)" } | |
default { . $eval } | |
} | |
} | |
} | |
} # end of function block | |
Export-ModuleMember -Function Invoke-MathExpression -Alias psCalc |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment