From 660fc9b2ee811ea28cec967d5de33144c9e73317 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 11:08:10 +0100 Subject: [PATCH 01/15] [misc] change usage of 'u32 literal to 'u64 'u32 was the literal used during development. Was auto converted to u64 as it's a literal, but better use correct one. --- measuremancer.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/measuremancer.nim b/measuremancer.nim index 8551c06..9c55663 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -108,7 +108,7 @@ proc procRes[T](res: T, grad: T, arg: Measurement[T]): Measurement[T] = der[key] = (grad * val).T # force to `T` to avoid `unchained` errors # σ is 0 if input's σ is zero, else it's ∂(f(x))/∂x * Δx =^= grad * arg.uncer let σ = if arg.uncer == T(0.0): T(0.0) else: abs((grad * arg.uncer).T) # force to `T` to avoid `unchained` errors - result = initMeasurement[T](res, σ, der, 0'u32) + result = initMeasurement[T](res, σ, der, 0'u64) proc derivative[T](a: Measurement[T], key: DerivKey[T]): T = result = if key in a.der: a.der[key] else: T(0.0) @@ -138,7 +138,7 @@ when false: # add (σ_x•∂G/∂x)² to total uncertainty (squared), but only if deriv != 0 resder[key] = ∂G_∂x err = err + ((σ_x * ∂G_∂x) * (σ_x * ∂G_∂x)) # convert product back to avoid `unchained` error - result = initMeasurement[T](res, sqrt(err), resder, 0'u32) + result = initMeasurement[T](res, sqrt(err), resder, 0'u64) proc procRes[T](res: T, grad: openArray[T], args: openArray[Measurement[T]]): Measurement[T] = ## Note: In the body of this we perform rather funny back and forth conversions between `T` @@ -169,7 +169,7 @@ proc procRes[T](res: T, grad: openArray[T], args: openArray[Measurement[T]]): Me err = (err + ((σ_x * ∂G_∂x) * (σ_x * ∂G_∂x)).T).T # convert product back to avoid `unchained` error result = initMeasurement[T](res, T(sqrt(err.float)), # convert to float and back to T to satisfy `unchained` - resder, 0'u32) + resder, 0'u64) proc `±`*[T: FloatLike](val, uncer: T): Measurement[T] = result = initMeasurement[T](val, uncer) From 44ef657de988b8e9847babe436c3bdbdf59c36f6 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 11:09:38 +0100 Subject: [PATCH 02/15] allow to disable unicode in printing of measurements Also changes the spacing in the unicode case to half width space. Compile with `-d:noUnicode` to print using `+-` and regular space instead. --- measuremancer.nim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/measuremancer.nim b/measuremancer.nim index 9c55663..4bd5934 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -184,7 +184,10 @@ proc measurement*[T: FloatLike](value, uncertainty: T): Measurement[T] = proc pretty*[T: FloatLike](m: Measurement[T], precision: int): string = let mval = m.val.float.formatBiggestFloat(precision = precision) let merr = m.uncer.float.formatBiggestFloat(precision = precision) - result = &"{mval} ± {merr}" + when not defined(noUnicode): + result = &"{mval} ± {merr}" + else: + result = &"{mval} +- {merr}" when not (T is float): result.add " " & $T From dfb0f871d674b99a7a52d488111dcfa4f3f50b6a Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 11:10:32 +0100 Subject: [PATCH 03/15] fix issue #6 by correctly using type information --- measuremancer.nim | 5 ++++- tests/tmeasuremancer.nim | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/measuremancer.nim b/measuremancer.nim index 4bd5934..79ae8d8 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -179,7 +179,10 @@ proc `±`*[T: FloatLike](val, uncer: T{lit}): Measurement[float] = result = initMeasurement[float](val.float, uncer.float) proc measurement*[T: FloatLike](value, uncertainty: T): Measurement[T] = - result = initMeasurement[float](value, uncertainty) + result = initMeasurement[T](value, uncertainty) + +proc measurement*[T: FloatLike](val, uncer: T{lit}): Measurement[float] = + result = initMeasurement[float](val.float, uncer.float) proc pretty*[T: FloatLike](m: Measurement[T], precision: int): string = let mval = m.val.float.formatBiggestFloat(precision = precision) diff --git a/tests/tmeasuremancer.nim b/tests/tmeasuremancer.nim index 4e8769c..c71bd7d 100644 --- a/tests/tmeasuremancer.nim +++ b/tests/tmeasuremancer.nim @@ -138,6 +138,13 @@ suite "Measurements of other types (e.g. unchained)": check( (k1 + k2).value.type is keV ) check( (k1 + k2).error.type is keV ) + test "Construction of `measurement` via proc": + let k1 = measurement(5.0.keV, 1.0.keV) + let k2 = measurement(2.5.keV, 1.5.keV) + check k1 + k2 =~= 7.5.keV ± 1.802775637731995.keV + check( (k1 + k2).value.type is keV ) + check( (k1 + k2).error.type is keV ) + test "Addition of incompatible units fails": let k1 = 5.0.keV ± 1.0.keV let m = 1.0.kg ± 0.1.kg From d9999cc7c9ec5d8ad576dcdef05dbbb7d78cb6c2 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 12:32:45 +0100 Subject: [PATCH 04/15] add note about derivatives for unchained units --- measuremancer.nim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/measuremancer.nim b/measuremancer.nim index 79ae8d8..8a672a6 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -24,6 +24,11 @@ type Derivatives[T] = OrderedTable[DerivKey[T], T] + ## *NOTE*: When dealing with `unchained` units, the derivative returned has the + ## wrong units! Instead of `T` it should be the type of `∂a(x)/∂x`, but + ## the code gets too complicated for the time being, as it would require use to + ## store different types for the actual measurement and the gradient, turning + ## `Measurement` into a double generic. Measurement*[T: FloatLike] = object val: T uncer: T @@ -111,6 +116,7 @@ proc procRes[T](res: T, grad: T, arg: Measurement[T]): Measurement[T] = result = initMeasurement[T](res, σ, der, 0'u64) proc derivative[T](a: Measurement[T], key: DerivKey[T]): T = + ## See note about derivative in definition of `Measurement` type result = if key in a.der: a.der[key] else: T(0.0) ## A "type safe" solution required for `unchained` could be achieved with something like this. From 42ed73fc1612ba581fdfc3d040ae205c12a039b6 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 12:33:11 +0100 Subject: [PATCH 05/15] fix equality for integers, bind local `toFloat` --- measuremancer.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/measuremancer.nim b/measuremancer.nim index 8a672a6..036b033 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -75,7 +75,11 @@ proc `==`*[T: FloatLike](k1, k2: DerivKey[T]): bool = ## value and uncertainty. ## TODO: should we use `almostEqual` here? Or is that too imprecise for the purpose? proc `==`*[T: FloatLike](m1, m2: Measurement[T]): bool = - result = almostEqual(m1.val, m2.val) and almostEqual(m1.uncer, m2.uncer) + ## comparison of two measurements does not need to take into account the + ## type, as we require same type anyway. Hence use `toFloat` to compare as float + bind toFloat # bind the locally defined `toFloat` + result = almostEqual(m1.val.toFloat, m2.val.toFloat) and + almostEqual(m1.uncer.toFloat, m2.uncer.toFloat) proc `==`*[T: FloatLike](m: Measurement[T], x: T): bool = result = almostEqual(m.val, x) and m.uncer == 0.0 @@ -87,6 +91,7 @@ proc isInf*[T: FloatLike](m: Measurement[T]): bool = m.val == Inf proc isNegInf*[T: FloatLike](m: Measurement[T]): bool = m.val == -Inf proc toFloat*[T: SomeFloat](x: T): float = x.float # float is biggest type, so this should be fine +proc toFloat*[T: SomeInteger](x: T): float = x.float # float is biggest type, so this should be fine proc initDerivatives[T](): Derivatives[T] = initOrderedTable[DerivKey[T], T]() From 169dcd7ba6c0106d374f9913eacc3373bd76bf30 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:29:57 +0100 Subject: [PATCH 06/15] export `to` procedure This is also useful for users of the library --- measuremancer.nim | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/measuremancer.nim b/measuremancer.nim index 036b033..22f4e38 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -18,6 +18,26 @@ type FloatLike* = concept x x.toFloat is float + ## A concept for any float like that is *not* an integer type + FloatLikeNotInt* = concept x, type T + x.toFloat is float + not (T is SomeInteger) + + ## A concept for types that are float like and whose application of + ## `pow` compiles and yields the same type. E.g. `float32`, but not + ## an `unchained` unit (does not support `pow`) + FloatLikeSupportsPow* = concept x, type T + x.toFloat is float + pow(x, 1.0) is T + + ## A concept for types that are float like and whose application of + ## `^` with a RT integer value compiles and yields the same type. E.g. `float32`, + ## but not an `unchained` unit (needs a `static int` to know the resulting + ## type at CT) + FloatLikeSupportsIntegerPowRT* {.explain.} = concept x, type T + x.toFloat is float + (x ^ 1) is T + IdType = uint64 # "`uint64` should be enough for everyone... uhhh" DerivKey[T] = tuple[val, uncer: T, tag: IdType] @@ -242,7 +262,7 @@ proc `-=`*[T: FloatLike](a: var Measurement[T], b: Measurement[T]) = ## Type conversion. TODO: make this more type safe, funnily enough -proc to[T: FloatLike; U](m: Measurement[T], dtype: typedesc[U]): Measurement[U] = +proc to*[T: FloatLike; U](m: Measurement[T], dtype: typedesc[U]): Measurement[U] = when T is U: result = m else: From 3dea616fccb274b83c083710dc9d338b3eb20508 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:32:16 +0100 Subject: [PATCH 07/15] fix division of literal / measurement --- measuremancer.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/measuremancer.nim b/measuremancer.nim index 22f4e38..efbc259 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -327,8 +327,11 @@ proc `/`*[T: FloatLike; U: FloatLike](m: Measurement[T], x: U): auto = assign1(m.val / x, 1.0 / x, m) ## Overloads for literals that force same type as Measurement has -proc `/`*[T: FloatLike; U: FloatLike](x: T{lit}, m: Measurement[U]): Measurement[U] = - result = procRes(U(U(x) / m.val), U(-x) / (m.val * m.val), m) +proc `/`*[T: FloatLike; U: FloatLike](x: T{lit}, m: Measurement[U]): auto = + type A = typeof( x / m.val ) + let arg: A = A(x / m.val) + let grad: A = A(-x / (m.val * m.val)) + result = procRes(arg, grad, m.to(A)) proc `/`*[U: FloatLike; T: FloatLike](m: Measurement[U], x: T{lit}): Measurement[U] = result = procRes(U(m.val / U(x)), 1.0 / U(x), m) From 3315c103043bf2a64a7ccdc86a739b6ed3833c75 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:33:01 +0100 Subject: [PATCH 08/15] fix return types of auto generated fn calls for math fns Must return `auto` as it must be based on the return type of `fn(x)` call. --- measuremancer.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/measuremancer.nim b/measuremancer.nim index efbc259..ca73c15 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -368,11 +368,12 @@ proc `^`*[T: FloatLike](a, b: Measurement[T]): Measurement[T] = a ** b import std / macros template genCall(fn, letStmt, x, m, deriv: untyped): untyped = - proc `fn`*[T](m: Measurement[T]): Measurement[T] = + proc `fn`*[T](m: Measurement[T]): auto = ## letStmt contains the definition of x, `let x = m.val` letStmt ## `deriv` is handed as an expression containing `x` - result = procRes(fn(x), deriv, m) + type U = typeof(fn(x)) + result = procRes(fn(x), deriv, m.to(U)) macro defineSupportedFunctions(body: untyped): untyped = result = newStmtList() From 3ddf6c6bd64c3baf21d63a9112b473f0499b75ce Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:33:54 +0100 Subject: [PATCH 09/15] add math_utils submodule containing `power` for static integers --- measuremancer/math_utils.nim | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 measuremancer/math_utils.nim diff --git a/measuremancer/math_utils.nim b/measuremancer/math_utils.nim new file mode 100644 index 0000000..076552c --- /dev/null +++ b/measuremancer/math_utils.nim @@ -0,0 +1,43 @@ +import std / macros + +# Taken from `unchained`. This won't be exported from `measuremancer` though, +# rather we will `bind` in the scope in which we use this. +macro power*(x: typed, num: static int): untyped = + ## general purpose power using `^` for integers, which works for any + ## type by rewriting to a product of `*`. + ## + ## For the special cases of -1, 0, 1 we simply rewrite to the correct + ## result. Negative powers are written as `1 / x^p` + if num == 0: + result = quote do: + `x` * typeof(`x`)(0.0) + elif num == 1: + result = x + elif num == -1: + ## Assume that the type supports addition by a float! + result = quote do: + 1.0 / `x` + else: + result = nnkInfix.newTree(ident"*") + + proc addInfix(n, x: NimNode, num: int) = + var it = n + if num > 0: + it.add nnkInfix.newTree(ident"*") + it[1].addInfix(x, num - 1) + while it.len < 3: + it.add x + + result.addInfix(x, abs(num) - 2) + + # invert if num is negative + if num < -1: + ## Assume that the type supports addition by a float! + result = quote do: + 1.0 / (`result`) + +import std/math +proc power*[T](x: T, num: SomeInteger): T = + if num > 0: result = x ^ num + elif num == 0: result = T(1) + else: result = T(1) / (x ^ abs(num)) From 176fba67221a075ef09c05cc19c29bcb84ab3864 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:34:27 +0100 Subject: [PATCH 10/15] fix power for different argument types, including fixing issue #7 --- measuremancer.nim | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/measuremancer.nim b/measuremancer.nim index ca73c15..08d9522 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -339,20 +339,40 @@ proc `/`*[U: FloatLike; T: FloatLike](m: Measurement[U], x: T{lit}): Measurement ## NOTE: Using any of the following exponentiation functions is dangerous. The Nim parser ## might include an additional prefix into the argument to `^` instead of keeping it as a, ## well, prefix. So `-(x - μ)^2` is parsed as `(-(x - μ))^2`! -proc `**`*[T: FloatLike](m: Measurement[T], p: Natural): Measurement[T] = - result = procRes(m.val ^ p, p.float * m.val ^ (p - 1), m) -proc `^`*[T: FloatLike](m: Measurement[T], p: Natural): Measurement[T] = m ** p - -proc `**`*[T: FloatLike](m: Measurement[T], p: FloatLike): Measurement[T] = - result = procRes(pow(m.val, p), p * pow(m.val, (p - 1.0)), m) -proc `^`*[T: FloatLike](m: Measurement[T], p: FloatLike): Measurement[T] = m ** p +from measuremancer / math_utils import power +## -> for static exponents +proc `**`*[T: FloatLike](m: Measurement[T], p: static SomeInteger): auto = + # Get the resulting type + type U = typeof(power(m.val, p)) + # and convert what is necessary. + result = procRes(U(power(m.val, p)), U(p.float * power(m.val, (p - 1))), m.to(U)) +proc `^`*[T: FloatLike](m: Measurement[T], p: static SomeInteger): auto = + ## NOTE: If you import `unchained`, this version is not actually used, as `unchained` + ## defines a macro `^` for static integer exponents, which simply rewrites + ## the AST to an infix (or trivial) node! + m ** p + +## -> for RT natural exponents +proc `**`*[T: FloatLikeSupportsIntegerPowRT](m: Measurement[T], p: SomeInteger): Measurement[T] = + result = procRes(m.val ^ p, p.float * power(m.val, (p - 1)), m) + +proc `^`*[T: FloatLikeSupportsIntegerPowRT](m: Measurement[T], p: SomeInteger): Measurement[T] = + m ** p + +## -> for explicit float exponents +proc `**`*[T: FloatLikeSupportsPow; U: FloatLikeNotInt]( + m: Measurement[T], p: U): Measurement[T] = + result = procRes(pow(m.val, p), p * pow(m.val, (p - 1.0)), m) +proc `^`*[T: FloatLikeSupportsPow; U: FloatLikeNotInt]( + m: Measurement[T], p: U): Measurement[T] = + m ** p proc `**`*[T: FloatLike](a, b: Measurement[T]): Measurement[T] = let powV = pow(a.val, b.val) result = procRes(powV, - [pow(a.val, b.val - 1.0), - powV * log(a.val)], + [pow(a.val, b.val - 1.0) * b.val, + powV * ln(a.val)], [a, b]) proc `^`*[T: FloatLike](a, b: Measurement[T]): Measurement[T] = a ** b From 9c87756d8865307fe28fe648a514457524378c91 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:36:02 +0100 Subject: [PATCH 11/15] [tests] extend tests for power and unchained trigonometric fns --- tests/tmeasuremancer.nim | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tests/tmeasuremancer.nim b/tests/tmeasuremancer.nim index c71bd7d..e5647ea 100644 --- a/tests/tmeasuremancer.nim +++ b/tests/tmeasuremancer.nim @@ -66,6 +66,50 @@ suite "Measurement & measurement": # √( 0.5² / 1.0² + 5.0² · 0.1² / 1.0⁴ ) = √ 0.5 = 0.707106781187 check y / x =~= 5.0 ± 0.7071067811865476 + test "Power (integer literal)": + let x = 2.0 ± 0.2 + # This should be: + # √( (2 · 2.0)² · 0.2² ) = 0.8 + check x ^ 2 == 4.0 ± 0.8 + check x ** 2 == 4.0 ± 0.8 + + test "Power (static integer)": + let x = 2.0 ± 0.2 + # This should be: + # √( (2 · 2.0)² · 0.2² ) = 0.8 + const y = 2 + check x ^ y == 4.0 ± 0.8 + check x ** y == 4.0 ± 0.8 + + test "Power (integer)": + let x = 2.0 ± 0.2 + # This should be: + # √( (2 · 2.0)² · 0.2² ) = 0.8 + let y = 2 + check x ^ y == 4.0 ± 0.8 + check x ** y == 4.0 ± 0.8 + + test "Power (float)": + let x = 2.0 ± 0.2 + # This should be: + # √( [ 2.5 · 2.0^{1.5} ]² · 0.2² ) = √ ( 50 · 0.² ) = sqrt(2) + check x ^ 2.5 == pow(2.0, 2.5) ± sqrt(2.0) + check x ** 2.5 == pow(2.0, 2.5) ± sqrt(2.0) + + test "Negative power (integer)": + let x = 2.0 ± 0.2 + # This should be: + # √( (-2 / x³)² · Δx² ) = √( (-2 / 2³)² · 0.2² ) = 0.05 + check x ^ (-2) == 1.0 / (x^2) + check x ** -2 == 1.0 / (x^2) + check x ^ -2 == 0.250 ± 0.0500 + + test "Power of two measurements": + let x = 2.0 ± 0.2 + let y = 5.0 ± 0.5 + check x ^ y == 32.0 ± 19.467818870203708 + check x ** y == 32.0 ± 19.467818870203708 + suite "Measurement and same symbol": # Simple correlations due to same symbol being used are correctly handled. # i.e. subtraction and division results in a measurement without error. @@ -159,3 +203,55 @@ suite "Measurements of other types (e.g. unchained)": check m * a =~= 9.81.kg•m•s⁻² ± 1.101072658819571.kg•m•s⁻² check( (m * a).value.type is kg•m•s⁻² ) check( (m * a).error.type is kg•m•s⁻² ) + + # integer powers can be supported for units. Float however not! (floats representing + # integer powers need to be converted to int manually!) + test "Power (integer literal)": + let x = 2.0.m ± 0.2.m + # This should be: + # √( (2 · 2.0)² · 0.2² ) = 0.8 + check x ^ 2 == 4.0.m² ± 0.8.m² + check x ** 2 == 4.0.m² ± 0.8.m² + + test "Power (static integer)": + let x = 2.0.m ± 0.2.m + # This should be: + # √( (2 · 2.0)² · 0.2² ) = 0.8 + const y = 2 + check x ^ y == 4.0.m² ± 0.8.m² + check x ** y == 4.0.m² ± 0.8.m² + + test "Negative power with units": + let x = 2.0.m ± 0.2.m + defUnit(Meter⁻²) + # This should be: + # √( (-2 / x³)² · Δx² ) = √( (-2 / 2³)² · 0.2² ) = 0.05 + check x ^ -2 == 1.0 / (x^2) + ## NOTE: As `**` does not use `unchained's` rewrite `^`, but rather + ## the regular `**` proc, the resulting unit is equivalent, but not + ## the *same Nim unit* (as they are `distinct` and `Meter⁻²` is not + ## declared in the measuremancer context. As such we cannot check the + ## sanity of the types at CT. + # TODO: investigate why this prints as `0.0500` instead of `0.050` + check $(x ** (-2)) == "0.250 ± 0.0500 Meter⁻²" + check (x ** -2).to(Meter⁻²) == to(1.0 / (x^2), Meter⁻²) + + test "Trigonometric functions only work with UnitLess": + block UnitLessCheck: + ## the division is just a simple way to get a `UnitLess` value + ## as on `unchained` HEAD (2022-11-16) `UnitLess` is not exported. + let x = (1.0.kg / 1.0.kg) ± (0.1.kg / 1.0.kg) + # This should be: + # √ (cos²(1.0) · 0.1²) + check typeof(x.value) is UnitLess + # application of `sin` returns a `float`! Valid units for `sin` are those convertible + # to `float` via converter defined in unchained + check typeof(sin(x)) is Measurement[float] + let exp = sin(x.value) ± sqrt(cos(x.value)^2 * (x.error)^2) + check typeof(exp) is Measurement[float] + check sin(x) =~= exp + block RadianCheck: # converts to UnitLess + let x = 1.0.Radian ± 0.1.Radian + # This should be: + # √ (cos²(1.0) · 0.1²) + check sin(x) =~= sin(1.0.Radian) ± sqrt(cos(1.0.Radian)^2 * 0.1.Radian^2) # 0.05403023058681398 From 9772bd0d96bd13493963801db49a188aafdf845c Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:36:25 +0100 Subject: [PATCH 12/15] update changelog --- changelog.org | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/changelog.org b/changelog.org index ecf92f8..6a3eaf4 100644 --- a/changelog.org +++ b/changelog.org @@ -1,3 +1,16 @@ +* v0.2.2 +- export ~to~ procedure (used to convert inner type of a + ~Measurement~) +- fix ~**~, ~^~ for two Measurements +- allow to disable printing of unicode symbols for string repr of + ~Measurement~ by compiling with ~-d:noUnicode~ +- fix issue #6 to allow ~measurement~ procedure to construct correct + type for given types other than ~float~ +- fix division of a literal with a measurement +- fix power math operation for different argument types, including + fixing issue #7 + - adds a new private submodule for a helper macro to generate + `power` calls for static integers (including negative) * v0.2.1 - change import path in test file to relative import * v0.2.0 From 30fabb2e497d1fb9d98fa09b1ec8b15bda0d14d3 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 18:48:56 +0100 Subject: [PATCH 13/15] fix copypasted dumb error in `power` macro --- measuremancer/math_utils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/measuremancer/math_utils.nim b/measuremancer/math_utils.nim index 076552c..4506d92 100644 --- a/measuremancer/math_utils.nim +++ b/measuremancer/math_utils.nim @@ -10,7 +10,7 @@ macro power*(x: typed, num: static int): untyped = ## result. Negative powers are written as `1 / x^p` if num == 0: result = quote do: - `x` * typeof(`x`)(0.0) + 1.0 elif num == 1: result = x elif num == -1: From bfee7e89a4f6f07fd8e02590e03dd00254967c9f Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 19:29:19 +0100 Subject: [PATCH 14/15] use `power` also in RT `SomeInteger` `^` procedure --- measuremancer.nim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/measuremancer.nim b/measuremancer.nim index 08d9522..0107c83 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -355,8 +355,7 @@ proc `^`*[T: FloatLike](m: Measurement[T], p: static SomeInteger): auto = ## -> for RT natural exponents proc `**`*[T: FloatLikeSupportsIntegerPowRT](m: Measurement[T], p: SomeInteger): Measurement[T] = - result = procRes(m.val ^ p, p.float * power(m.val, (p - 1)), m) - + result = procRes(power(m.val, p), p.float * power(m.val, (p - 1)), m) proc `^`*[T: FloatLikeSupportsIntegerPowRT](m: Measurement[T], p: SomeInteger): Measurement[T] = m ** p From d23e4598d28a2dcaa0b5662f1eecf70f15372450 Mon Sep 17 00:00:00 2001 From: Vindaar Date: Wed, 16 Nov 2022 19:45:35 +0100 Subject: [PATCH 15/15] for nim < 1.7 do not restrict Measurement to `FloatLike` No idea what the hell is going on in 1.6, but if it's restricted to FloatLike, we get a "cannot instantiate Measurement" error from the `**` procedure, shrug. --- measuremancer.nim | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/measuremancer.nim b/measuremancer.nim index 0107c83..20d3b8d 100644 --- a/measuremancer.nim +++ b/measuremancer.nim @@ -34,7 +34,7 @@ type ## `^` with a RT integer value compiles and yields the same type. E.g. `float32`, ## but not an `unchained` unit (needs a `static int` to know the resulting ## type at CT) - FloatLikeSupportsIntegerPowRT* {.explain.} = concept x, type T + FloatLikeSupportsIntegerPowRT* = concept x, type T x.toFloat is float (x ^ 1) is T @@ -49,11 +49,21 @@ type ## the code gets too complicated for the time being, as it would require use to ## store different types for the actual measurement and the gradient, turning ## `Measurement` into a double generic. - Measurement*[T: FloatLike] = object - val: T - uncer: T - id: IdType - der: Derivatives[T] # map of the derivatives +when (NimMajor, NimMinor, NimPatch) < (1, 7, 0): + type + Measurement*[T] = object + val: T + uncer: T + id: IdType + der: Derivatives[T] # map of the derivatives +else: + type + Measurement*[T: FloatLike] = object + val: T + uncer: T + id: IdType + der: Derivatives[T] # map of the derivatives + func value*[T: FloatLike](m: Measurement[T]): T {.inline.} = m.val func uncertainty*[T: FloatLike](m: Measurement[T]): T {.inline.} = m.uncer