0.2.0 Gauss

This commit is contained in:
Rob Tillaart 2023-07-10 20:25:09 +02:00
parent 33e70b036d
commit 2551f5efe1
16 changed files with 476 additions and 109 deletions

View File

@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.0] - 2023-07-10
- improve performance => faster lookup.
- removed __z[] saves 136 bytes RAM
- remove MultiMap dependency.
- - remove **\_stddev** as **\_reciprokeSD** holds same information.
- add **float P_outside(float f, float g)**
- returns **P(x < f) + P(g < x)** under condition (f < g).
- add **float denormalize(float value)** (reverse normalize).
- add examples DS18B20 and HX711 and BMI
- update readme.md
- minor edits.
----
## [0.1.1] - 2023-07-07
- improve performance => reciprokeSD = 1.0/stddev
- update readme.md
@ -19,8 +34,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- add **float getStdDev()** convenience function.
- clean up a bit
## [0.1.0] - 2023-07-06
- initial version

View File

@ -1,7 +1,7 @@
//
// FILE: Gauss.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.1.1
// VERSION: 0.2.0
// PURPOSE: Library for the Gauss probability math.
// DATE: 2023-07-06

View File

@ -2,15 +2,14 @@
//
// FILE: Gauss.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.1
// VERSION: 0.2.0
// PURPOSE: Library for the Gauss probability math.
// DATE: 2023-07-06
#include "Arduino.h"
#include "MultiMap.h"
#define GAUSS_LIB_VERSION (F("0.1.1"))
#define GAUSS_LIB_VERSION (F("0.2.0"))
class Gauss
@ -19,7 +18,6 @@ public:
Gauss()
{
_mean = 0;
_stddev = 1;
_reciprokeSD = 1;
}
@ -27,8 +25,8 @@ public:
bool begin(float mean = 0, float stddev = 1)
{
_mean = mean;
_stddev = stddev; // should be positive
_reciprokeSD = 1.0 / _stddev;
if (stddev == 0) _reciprokeSD = NAN;
else _reciprokeSD = 1.0 / stddev;
return (stddev > 0);
}
@ -41,14 +39,13 @@ public:
float getStdDev()
{
return _stddev;
return 1.0 / _reciprokeSD;
}
float P_smaller(float value)
{
if (_stddev == 0) return NAN;
// normalize(value)
if (_reciprokeSD == NAN) return NAN;
return _P_smaller((value - _mean) * _reciprokeSD);
}
@ -61,18 +58,25 @@ public:
float P_between(float p, float q)
{
if (_stddev == 0) return NAN;
if (_reciprokeSD == NAN) return NAN;
if (p >= q) return 0;
return P_smaller(q) - P_smaller(p);
}
float P_outside(float p, float q)
{
return 1.0 - P_between(p, q);
}
float P_equal(float value)
{
if (_stddev == 0) return NAN;
if (_reciprokeSD == NAN) return NAN;
float n = (value - _mean) * _reciprokeSD;
// gain of ~10% if we allocate a global var for 'constant' c
float c = _reciprokeSD * (1.0 / sqrt(TWO_PI));
return c * exp(-0.5 * n * n);
return c * exp(-0.5 * (n * n));
}
@ -88,18 +92,32 @@ public:
}
float denormalize(float value)
{
return value / _reciprokeSD + _mean;
}
float bellCurve(float value)
{
return P_equal(value);
}
float CDF(float value)
{
return P_smaller(value);
}
private:
float _P_smaller(float x)
{
// NORM.DIST(mean, stddev, x, true)
// these points correspond with
// 0.0 .. 3.0 in steps of 0.1 followed by 4.0, 5.0 and 6.0
float __gauss[] = {
0.50000000, 0.53982784, 0.57925971, 0.61791142,
0.65542174, 0.69146246, 0.72574688, 0.75803635,
@ -112,24 +130,37 @@ private:
0.99999971, 1.00000000
};
// 0..60000 uint16_t = 68 bytes less
float __z[] = {
0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7,
0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5,
1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3,
2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 4,0,
5.0, 6.0
};
bool neg = false;
if (x < 0)
{
neg = true;
x = -x;
}
// a dedicated MultiMap could exploit the fact that
// the __z[] array is largely equidistant.
// that could remove the __z[] array (almost) completely.
if (x < 0) return 1.0 - multiMap<float>(-x, __z, __gauss, 34);
return multiMap<float>(x, __z, __gauss, 34);
if (x >= 6.0)
{
if (neg) return 0.0;
return 1.0;
}
if (x <= 3.0)
{
int idx = x * 10;
float rv = __gauss[idx] + ((x * 10) - idx) * (__gauss[idx+1] - __gauss[idx]);
if (neg) return 1.0 - rv;
return rv;
}
// 3.0 .. 6.0
int xint = x;
int idx = 27 + xint;
float rv = __gauss[idx] + (x - xint) * (__gauss[idx+1] - __gauss[idx]);
if (neg) return 1.0 - rv;
return rv;
}
float _mean = 0;
float _stddev = 1; // not needed as _reciprokeSD holds same info?
// reciprokeSD = 1.0 / stddev is faster in most math (MUL vs DIV)
float _reciprokeSD = 1;
};

View File

@ -8,7 +8,7 @@
# Gauss
Library for the Gauss probability math.
Library for the Gauss probability math. (Normal Distribution).
## Description
@ -17,31 +17,47 @@ Gauss is an experimental Arduino library to approximate the probability that a v
smaller or larger than a given value.
These under the premises of a Gaussian distribution with parameters **mean** and **stddev**
(a.k.a. average / mu / µ and standard deviation / sigma / σ).
If these parameters are not given, 0 and 1 are used by default (normalized Gaussian distribution).
If these parameters are not given, mean == 0 and stddev == 1 are used by default.
This is the normalized Gaussian distribution.
The values are approximated with **MultiMap()** using a 32 points interpolated lookup.
Therefore the **MultiMap** library need to be downloaded too (see related below).
The number of lookup points might chance in the future.
The values of the functions are approximated with a **MultiMap()** based lookup
using a 34 points interpolated lookup.
- Version 0.1.x used the **MultiMap** library need to be downloaded too (see related below).
- Version 0.2.0 and above embeds an optimized version, so no need to use **MultiMap**.
Return values are given as floats, if one needs percentages, just multiply by 100.0.
Note: The number of lookup points might chance in the future, keeping a balance between
accuracy and footprint.
#### Accuracy
#### Accuracy / precision
The lookup table has 34 points with 8 decimals.
The version 0.2.0 lookup table has 34 points with 8 decimals.
This matches the precision of float data type.
Do not expect a very high accuracy / precision as interpolation is linear.
For many applications this accuracy is sufficient.
Do not expect an 8 decimals accuracy / precision as interpolation is linear.
Values of the table are calculated with ```NORM.DIST(mean, stddev, x, true)```.
A first investigation (part 0.0 - 1.3) shows:
- maximum error ~ 0.0003016 <= 0.031%
- average error ~ 0.0001433 <= 0.015%
I expect that for many applications this accuracy is probably sufficient.
The 34 points are in a (mostly) equidistant table.
Searching the interpolation points is optimized in version 0.2.0.
The table uses the symmetry of the distribution to reduce the number of points.
Values of the table are calculated with ```NORM.DIST(x, mean, stddev, true)```
spreadsheet function.
Note: 0.1.0 was 32 points 4 decimals. Need to investigate reduction of points.
#### Applications
- use as a filter? do not allow > 3 sigma
- compare historic data to current data
- compare population data with individual
- use as a filter e.g. detect above N1 sigma and under N2 sigma
- compare historic data to current data e.g. temperature.
- transforming to sigma makes it scale C / F / K independent.
- fill a bag (etc) until a certain weight is reached (+- N sigma)
- compare population data with individual, e.g. Body Mass Index (BMI).
#### Character
@ -50,6 +66,7 @@ Note: 0.1.0 was 32 points 4 decimals. Need to investigate reduction of points.
|:-----------:|:------:|:----------:|:-----:|
| mean | mu | ALT-230 | µ |
| stddev | sigma | ALT-229 | σ |
| CDF | phi | ALT-232 | Φ | ALT-237 for lower case
- https://altcodesguru.com/greek-alt-codes.html
@ -74,38 +91,51 @@ Note: 0.1.0 was 32 points 4 decimals. Need to investigate reduction of points.
- **Gauss()** constructor. Uses mean = 0 and stddev = 1 by default.
- **bool begin(float mean = 0, float stddev = 1)** set the mean and stddev.
Returns true if stddev > 0 which should be so.
Returns false if stddev <= 0, which could be a user choice.
Returns false if stddev <= 0, however it could be a user choice to use this.
Note that if ```stddev == 0```, probabilities cannot be calculated
as the distribution is not Gaussian.
The default values (0,1) gives the normalized Gaussian distribution.
**begin()** can be called at any time to change the mean or stddev.
The default values (0, 1) gives the normalized Gaussian distribution.
**begin()** can be called at any time to change the mean and/or stddev.
- **float getMean()** returns current mean.
- **float getStddev()** returns current stddev.
#### Probability
Probability functions return NAN if stddev == 0.
Probability functions return NAN if stddev == 0.
Return values are given as a float 0.0 .. 1.0.
Multiply probabilities by 100.0 to get the value as a percentage.
- **float P_smaller(float f)** returns probability **P(x < f)**.
Multiply by 100.0 to get the value as a percentage.
A.k.a. **CDF()** Cumulative Distribution Function.
- **float P_larger(float f)** returns probability **P(x > f)**.
Multiply by 100.0 to get the value as a percentage.
As the distribution is continuous **P_larger(f) == 1 - P_smaller(f)**.
- **float P_between(float f, float g)** returns probability **P(f < x < g)**.
Multiply by 100.0 to get the value as a percentage.
- if f >= g ==> returns 1.0
- **float P_equal(float f)** returns probability **P(x == f)**.
This uses the bell curve formula.
- **float P_outside(float f, float g)** returns probability **P(x < f) + P(g < x)**.
- note that f should be smaller or equal to g
- **P_outside() = 1 - P_between()**
#### Normalize
- **float normalize(float f)** normalize a value to normalized distribution.
E.g if mean == 50 and stddev == 14, then 71 ==> +1.5 sigma.
Is equal to number of **stddevs()**.
- **float denormalize(float f)** reverses normalize().
What value would have a deviation of 1.73 stddev.
- **float stddevs(float f)** returns the number of stddevs from the mean.
Identical to **normalize()**.
#### Other
- **float normalize(float f)** normalize a value to normalized distribution.
Is equal to number of **stddevs()**.
- **float stddevs(float f)** returns the number of stddevs from the mean.
E.g if mean == 50 and stddev == 14, then 71 ==> +1.5 sigma.
wrapper functions:
- **float bellCurve(float f)** returns probability **P(x == f)**.
- **float CDF(float f)** returns probability **P(x < f)**.
## Performance
@ -114,26 +144,26 @@ Indicative numbers for 1000 calls, timing in micros.
Arduino UNO, 16 MHz, IDE 1.8.19
| function | 0.1.0 | 0.1.1 | notes |
|:--------------|:--------:|:--------:|:--------|
| P_smaller | 375396 | 365964 |
| P_larger | 384368 | 375032 |
| P_between | 265624 | 269176 |
| normalize | 44172 | 23024 |
| bellCurve | 255728 | 205460 |
| approx.bell | 764028 | 719184 | see examples
| function | 0.1.0 | 0.1.1 | 0.2.0 | notes |
|:--------------|:--------:|:--------:|:--------:|:--------|
| P_smaller | 375396 | 365964 | 159536 |
| P_larger | 384368 | 375032 | 169056 |
| P_between | 265624 | 269176 | 150148 |
| normalize | 44172 | 23024 | 23024 |
| bellCurve | 255728 | 205460 | 192524 |
| approx.bell | 764028 | 719184 | 333172 | see examples
ESP32, 240 MHz, IDE 1.8.19
| function | 0.1.0 | 0.1.1 | notes |
|:--------------|:--------:|:--------:|:--------|
| P_smaller | - | 4046 |
| P_larger | - | 4043 |
| P_between | - | 3023 |
| normalize | - | 592 |
| bellCurve | - | 13522 |
| approx.bell | - | 7300 |
| function | 0.1.0 | 0.1.1 | 0.2.0 | notes |
|:--------------|:--------:|:--------:|:--------:|:--------|
| P_smaller | - | 4046 | 1498 |
| P_larger | - | 4043 | 1516 |
| P_between | - | 3023 | 1569 |
| normalize | - | 592 | 585 |
| bellCurve | - | 13522 | 13133 |
| approx.bell | - | 7300 | 2494 |
## Future
@ -141,33 +171,19 @@ ESP32, 240 MHz, IDE 1.8.19
#### Must
- documentation
- test test test
#### Should
- optimize accuracy
- revisit lookup of MultiMap
- (-10 .. 0) might be more accurate (significant digits)?
- double instead of floats? (good table?)
- make use of equidistant \_\_z\[] table
#### Could
- add examples
- e.g. temperature (DS18B20 or DHT22)
- e.g. loadcell (HX711)
- embed MultiMap hardcoded instead of library dependency
- add unit tests
- remove **\_stddev** as **\_reciprokeSD** holds same information.
- reverse normalization
- G(100,25) which value has stddev 0.735?
- **VAL(probability = 0.75)** ==> 134 whatever
- Returns the value of the distribution for which the **CDF()** is at least probability.
- Inverse of **P_smaller()**
- **float P_outside(float f, float g)** returns probability **P(x < f) + P(g < x)**.
- assuming no overlap. Use **P_outside() = 1 - P_between()**
- Returns the value for which the **CDF()** is at least probability.
- Inverse of **P_smaller()** (how? binary search)
#### Won't (unless requested)
@ -177,5 +193,5 @@ ESP32, 240 MHz, IDE 1.8.19
- move code to .cpp file? (rather small lib).
- **void setMean(float f)** can be done with begin()
- **void setStddev(float f)** can be done with begin()
- optimize accuracy
- (-6 .. 0) might be more accurate (significant digits)?

View File

@ -0,0 +1,74 @@
//
// FILE: Gauss_BMI.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo - not a medical program
#include "Gauss.h"
Gauss G;
void setup(void)
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("GAUSS_LIB_VERSION:\t");
Serial.println(GAUSS_LIB_VERSION);
Serial.println();
// BMI - adjust to your local situation
// https://en.wikipedia.org/wiki/List_of_sovereign_states_by_body_mass_index
// stddev is assumption.
G.begin(25.0, 2.5);
}
void loop(void)
{
// flush
while (Serial.available()) Serial.read();
Serial.println("Enter your weight in kg: ");
while (!Serial.available());
float weight = Serial.parseFloat();
if (weight == 0) return;
while (Serial.available()) Serial.read();
Serial.println("Enter your length in cm: ");
while (!Serial.available());
float length = Serial.parseFloat();
if (length == 0) return;
float BMI = (10000.0 * weight) / (length * length);
float stddev = G.normalize(BMI);
Serial.print("\tWeight:\t\t");
Serial.println(weight);
Serial.print("\tLength:\t\t");
Serial.println(length);
Serial.print("\tBMI:\t\t");
Serial.println(BMI);
Serial.print("\tStddev:\t\t");
if (stddev >= 0) Serial.print('+');
Serial.println(stddev, 2);
Serial.print("\tSmaller:\t");
Serial.print(100.0 * G.P_smaller(BMI));
Serial.println("%");
Serial.print("\tLarger:\t\t");
Serial.print(100.0 * G.P_larger(BMI));
Serial.println("%");
Serial.print("\tSame:\t\t");
Serial.print(100.0 * G.P_between(BMI - 0.1, BMI + 0.1));
Serial.print("%");
Serial.println();
Serial.println();
}
// -- END OF FILE --

View File

@ -0,0 +1,35 @@
platforms:
rpipico:
board: rp2040:rp2040:rpipico
package: rp2040:rp2040
gcc:
features:
defines:
- ARDUINO_ARCH_RP2040
warnings:
flags:
packages:
rp2040:rp2040:
url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
# - due
# - zero
# - leonardo
- m4
#- esp32
- esp8266
# - mega2560
- rpipico
libraries:
- MultiMap
- DS18B20_RT
unittest:
# These dependent libraries will be installed
libraries:
- MultiMap

View File

@ -0,0 +1,59 @@
//
// FILE: Gauss_DS18B20.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
//
// can be used with plotter
#include "DS18B20.h"
#include "Gauss.h"
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DS18B20 sensor(&oneWire);
Gauss G;
void setup(void)
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("GAUSS_LIB_VERSION:\t");
Serial.println(GAUSS_LIB_VERSION);
Serial.print("DS18B20_LIB_VERSION:\t");
Serial.println(DS18B20_LIB_VERSION);
Serial.println();
// default temperature + stddev
// adjust to your local situation
G.begin(26.00, 0.2);
sensor.begin();
sensor.setResolution(12);
Serial.println("\ndone...");
}
void loop(void)
{
// simulation
// float temp = 18 + random(800) * 0.01;
sensor.requestTemperatures();
while (!sensor.isConversionComplete());
float temp = sensor.getTempC();
float stddev = G.normalize(temp);
Serial.print("TEMP: ");
Serial.print(temp, 2);
Serial.print("\tSTDDEV: ");
if (stddev >= 0) Serial.print('+');
Serial.print(stddev, 2);
Serial.println();
}
// -- END OF FILE --

View File

@ -0,0 +1,35 @@
platforms:
rpipico:
board: rp2040:rp2040:rpipico
package: rp2040:rp2040
gcc:
features:
defines:
- ARDUINO_ARCH_RP2040
warnings:
flags:
packages:
rp2040:rp2040:
url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
# - due
# - zero
# - leonardo
- m4
- esp32
- esp8266
# - mega2560
- rpipico
libraries:
- MultiMap
- HX711
unittest:
# These dependent libraries will be installed
libraries:
- MultiMap

View File

@ -0,0 +1,69 @@
//
// FILE: Gauss_HX711.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
//
// can be used with plotter
#include "HX711.h"
#include "Gauss.h"
HX711 scale;
uint8_t dataPin = 6;
uint8_t clockPin = 7;
Gauss G;
void setup(void)
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("GAUSS_LIB_VERSION:\t");
Serial.println(GAUSS_LIB_VERSION);
Serial.print("HX711_LIB_VERSION:\t");
Serial.println(HX711_LIB_VERSION);
Serial.println();
// default weight in grams + stddev
// adjust to your local situation
G.begin(100.0, 0.75);
scale.begin(dataPin, clockPin);
// load cell factor 5 KG
// TODO: calibrate the load cell.
scale.set_scale(420.0983);
// reset the scale to zero = 0
scale.tare();
Serial.println("you may now add weights.");
}
void loop(void)
{
// simulation
// float weight = 249 + random(20) * 0.1;
float weight = scale.get_units(5);
float stddev = G.normalize(weight);
Serial.print("WEIGHT: ");
Serial.print(weight, 1);
Serial.print("\tSTDDEV: ");
if (stddev >= 0) Serial.print('+');
Serial.print(stddev, 2);
Serial.print("\t");
if (stddev < -1) Serial.print("LOW."); // TOO LOW
else if (stddev > 1) Serial.print("HIGH.");
else Serial.print("OK.");
Serial.println();
delay(1000);
}
// -- END OF FILE --

View File

@ -150,5 +150,4 @@ void test_6()
}
// -- END OF FILE --

View File

@ -0,0 +1,17 @@
Arduino UNO
IDE 1.8.19
Gauss_performance.ino
GAUSS_LIB_VERSION: 0.2.0
Timing in micros (1000 calls)
P_smaller: 159536
P_larger: 169056
P_between: 150148
normalize: 23024
bellCurve: 192524
approx.bell: 333172
done...

View File

@ -17,9 +17,9 @@ void setup(void)
Serial.println();
test_1();
// test_2();
// test_3();
// test_4();
test_2();
test_3();
test_4();
Serial.println("\ndone...");
}

View File

@ -2,7 +2,8 @@
// FILE: Gauss_test_bell_curve.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
//
// use plotter to create the graph in two ways
#include "Gauss.h"
@ -42,6 +43,7 @@ void get_bell_curve()
}
// approximation of the bell curve
void approximate()
{
G.begin(0, 1);
@ -49,8 +51,9 @@ void approximate()
for (float f = -5.0; f <= 5.0; f += 0.01)
{
// width == 2x 0.5233 == 1.0466
// 0.0466 heuristic correction factor to match peak of bellCurve()
// not exact match but almost perfect
// 0.0466 heuristic correction factor to match peak of the bell curve()
// not an exact match but almost perfect
// cf found with separate function below
float cf = 0.52330751;
float a = G.P_smaller(f - cf);
float b = G.P_smaller(f + cf);
@ -60,7 +63,7 @@ void approximate()
}
// find the correction factor for the appoximate function
// find the correction factor for the approximate function
// so the peak matches the bell curve function.
// ==> 0.52330751
void find_correction_factor()

View File

@ -15,15 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/Gauss.git"
},
"dependencies":
[
{
"owner": "Rob Tillaart",
"name": "MultiMap",
"version": "^0.1.7"
}
],
"version": "0.1.1",
"version": "0.2.0",
"license": "MIT",
"frameworks": "arduino",
"platforms": "*",

View File

@ -1,5 +1,5 @@
name=Gauss
version=0.1.1
version=0.2.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Library for the Gauss probability math.
@ -8,5 +8,5 @@ category=Data Processing
url=https://github.com/RobTillaart/Gauss
architectures=*
includes=Gauss.h
depends=MultiMap
depends=

View File

@ -103,6 +103,22 @@ unittest(test_P_between)
}
unittest(test_P_outside)
{
Gauss G;
G.begin(0, 1);
assertEqualFloat(0.5013, G.P_outside(-3.0, 0.0), 0.0001);
assertEqualFloat(0.5228, G.P_outside(-2.0, 0.0), 0.0001);
assertEqualFloat(0.6587, G.P_outside(-1.0, 0.0), 0.0001);
assertEqualFloat(1.0000, G.P_outside(0.0, 0.0), 0.0001);
assertEqualFloat(0.6587, G.P_outside(0.0, 1.0), 0.0001);
assertEqualFloat(0.5228, G.P_outside(0.0, 2.0), 0.0001);
assertEqualFloat(0.5013, G.P_outside(0.0, 3.0), 0.0001);
}
unittest(test_P_equal)
{
Gauss G;
@ -132,6 +148,14 @@ unittest(test_normailze)
assertEqualFloat(1.0, G.normalize(125), 0.0001);
assertEqualFloat(2.0, G.normalize(150), 0.0001);
assertEqualFloat(3.0, G.normalize(175), 0.0001);
assertEqualFloat(25, G.denormalize(-3.0), 0.0001);
assertEqualFloat(50, G.denormalize(-2.0), 0.0001);
assertEqualFloat(75, G.denormalize(-1.0), 0.0001);
assertEqualFloat(100, G.denormalize(0.0), 0.0001);
assertEqualFloat(125, G.denormalize(1.0), 0.0001);
assertEqualFloat(150, G.denormalize(2.0), 0.0001);
assertEqualFloat(175, G.denormalize(3.0), 0.0001);
}