Merge pull request #1108 from rowyio/fix/percentage-precision-error

Fix/percentage precision error
This commit is contained in:
Shams
2023-02-06 08:53:00 +01:00
committed by GitHub
5 changed files with 143 additions and 5 deletions

View File

@@ -2,6 +2,7 @@ import { IDisplayCellProps } from "@src/components/fields/types";
import { useTheme } from "@mui/material";
import { resultColorsScale } from "@src/utils/color";
import { multiply100WithPrecision } from "./utils";
export default function Percentage({ column, value }: IDisplayCellProps) {
const theme = useTheme();
@@ -34,7 +35,7 @@ export default function Percentage({ column, value }: IDisplayCellProps) {
zIndex: 1,
}}
>
{Math.round(percentage * 100)}%
{multiply100WithPrecision(percentage)}%
</div>
</>
);

View File

@@ -1,13 +1,20 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/TableCell/EditorCellTextField";
import { multiply100WithPrecision, divide100WithPrecision } from "./utils";
export default function Percentage(props: IEditorCellProps<number>) {
return (
<EditorCellTextField
{...(props as any)}
InputProps={{ type: "number", endAdornment: "%" }}
value={typeof props.value === "number" ? props.value * 100 : props.value}
onChange={(v) => props.onChange(Number(v) / 100)}
value={
typeof props.value === "number"
? multiply100WithPrecision(props.value)
: props.value
}
onChange={(v) => {
props.onChange(divide100WithPrecision(Number(v)));
}}
/>
);
}

View File

@@ -16,6 +16,7 @@ import { ISettingsProps } from "@src/components/fields/types";
import { Color, toColor } from "react-color-palette";
import { fieldSx } from "@src/components/SideDrawer/utils";
import { resultColorsScale, defaultColors } from "@src/utils/color";
import { multiply100WithPrecision } from "./utils";
const colorLabels: { [key: string]: string } = {
0: "Start",
@@ -160,7 +161,7 @@ const Preview = ({ colors }: { colors: any }) => {
}}
/>
<Typography style={{ position: "relative", zIndex: 1 }}>
{Math.floor(value * 100)}%
{multiply100WithPrecision(value)}%
</Typography>
</Box>
);

View File

@@ -3,6 +3,7 @@ import { ISideDrawerFieldProps } from "@src/components/fields/types";
import { TextField, InputAdornment, Box, useTheme } from "@mui/material";
import { resultColorsScale } from "@src/utils/color";
import { getFieldId } from "@src/components/SideDrawer/utils";
import { multiply100WithPrecision } from "./utils";
export default function Percentage({
column,
@@ -20,7 +21,9 @@ export default function Percentage({
margin="none"
onChange={(e) => onChange(Number(e.target.value) / 100)}
onBlur={onSubmit}
value={typeof value === "number" ? value * 100 : value}
value={
typeof value === "number" ? multiply100WithPrecision(value) : value
}
id={getFieldId(column.key)}
label=""
hiddenLabel

View File

@@ -0,0 +1,126 @@
import { trim, trimEnd } from "lodash-es";
/**
* Multiply a number by 100 and return a string without floating point error
* by shifting the decimal point 2 places to the right as a string
* e.g. floating point error: 0.07 * 100 === 7.000000000000001
*
* A few examples:
*
* let number = 0.07;
* console.log(number, multiply100WithPrecision(number));
* --> 7
*
* number = 0;
* console.log(number, multiply100WithPrecision(number));
* --> 0
*
* number = 0.1;
* console.log(number, multiply100WithPrecision(number));
* --> 10
*
* number = 0.001;
* console.log(number, multiply100WithPrecision(number));
* --> 0.1
*
* number = 0.00001;
* console.log(number, multiply100WithPrecision(number));
* --> 0.001
*
* number = 100;
* console.log(number, multiply100WithPrecision(number));
* --> 10000
*
* number = 1999.99;
* console.log(number, multiply100WithPrecision(number));
* --> 199999
*
* number = 1999.999;
* console.log(number, multiply100WithPrecision(number));
* --> 199999.9
*
* number = 0.25;
* console.log(number, multiply100WithPrecision(number));
* --> 25
*
* number = 0.15;
* console.log(number, multiply100WithPrecision(number));
* --> 15
*
* number = 1.23456789;
* console.log(number, multiply100WithPrecision(number));
* --> 123.456789
*
* number = 0.0000000001;
* console.log(number, multiply100WithPrecision(number));
* --> 1e-8
*/
export const multiply100WithPrecision = (value: number) => {
if (value === 0) {
return 0;
}
let valueString = value.toString();
// e.g 1e-10 becomes 1e-8
if (valueString.includes("e")) {
return value * 100;
}
// if the number is integer, add .00
if (!valueString.includes(".")) {
valueString = valueString.concat(".00");
}
let [before, after] = valueString.split(".");
// if after decimal has only 1 digit, pad a 0
if (after.length === 1) {
after = after.concat("0");
}
let newNumber = `${before}${after.slice(0, 2)}.${after.slice(2)}`;
newNumber = trimEnd(trim(newNumber, "0"), ".");
if (newNumber.startsWith(".")) {
newNumber = "0" + newNumber;
}
return Number(newNumber);
};
/**
* Divide a number by 100 and return a string without floating point error
* by shifting the decimal point 2 places to the left as a string
*/
export const divide100WithPrecision = (value: number) => {
if (value === 0) {
return 0;
}
let valueString = value.toString();
// e.g 1e-10 becomes 1e-8
if (valueString.includes("e")) {
return value / 100;
}
// add decimal if integer
if (!valueString.includes(".")) {
valueString = valueString + ".";
}
let [before, after] = valueString.split(".");
// if before decimal has less than digit, pad 0
if (before.length < 2) {
before = "00" + before;
}
let newNumber = `${before.slice(0, before.length - 2)}.${before.slice(
before.length - 2
)}${after}`;
newNumber = trimEnd(trimEnd(newNumber, "0"), ".");
if (newNumber.startsWith(".")) {
newNumber = "0" + newNumber;
}
return Number(newNumber);
};