-
-
Notifications
You must be signed in to change notification settings - Fork 275
fix(3742): improve user segmentation with BigInt-based random generation #5110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
c650ada
3e155c1
87cd8f5
8724cc3
3ae4dd8
b3a2a69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,25 +1,45 @@ | ||||||
| import type { Json } from '@metamask/utils'; | ||||||
| import { validate as uuidValidate, version as uuidVersion } from 'uuid'; | ||||||
|
|
||||||
| import type { FeatureFlagScopeValue } from '../remote-feature-flag-controller-types'; | ||||||
|
|
||||||
| /* eslint-disable no-bitwise */ | ||||||
| /** | ||||||
| * Generates a deterministic random number between 0 and 1 based on a metaMetricsId. | ||||||
| * This is useful for A/B testing and feature flag rollouts where we want | ||||||
| * consistent group assignment for the same user. | ||||||
| * | ||||||
| * @param metaMetricsId - The unique identifier used to generate the deterministic random number | ||||||
| * Supports two metaMetricsId formats: | ||||||
| * - UUIDv4 format (new mobile implementation) | ||||||
| * - Hex format with 0x prefix (extension or old mobile implementation) | ||||||
| * | ||||||
| * For UUIDv4 format, the following normalizations are applied: | ||||||
| * - Removes all dashes from the UUID | ||||||
| * - Remove version (4) bits and replace with 'f' | ||||||
|
||||||
| * - Remove version (4) bits and replace with 'f' | |
| * - Remove version (4) bits |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, though I notice that the test does show that the minimum UUID does result in zero. Odd. It looks like the result would be skewed to me though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I tested it and it does skew the results, but by too small an amount. You need to use a precision of 10^15 to see a non-zero result from the minimum input, but we're only using precision of 10^6.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using an implicit minimum of zero, and a maximum of 'f' * length, perhaps we can achieve perfect distribution by updating the final calculation to something like this:
function uuidToBigInt(id: string) {
return BigInt(`0x${uuid.replace(/-/gu, '')}`);
}
const MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';
const MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff';
const MIN_UUID_V4_BIGINT = uuidToBigInt(MIN_UUI_V4);
const MAX_UUID_V4_BIGINT = uuidToBigInt(MAX_UUI_V4);
...
export function generateDeterministicRandomNumber(
metaMetricsId: string,
): number {
let idValue: BigInt;
let maxValue: BigInt;
// uuidv4 format
if (uuidValidate(metaMetricsId) && uuidVersion(metaMetricsId) === 4) {
// Adjust both values by subtracting the minimum value, so that the result isn't biased away from zero
idValue = uuidToBigInt(metaMetricsId) - MIN_UUID_V4_BIGINT;
maxId = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT;
} else {
// hex format with 0x prefix
idValue = BigInt(`0x${metaMetricsId.slice(2)}`;
maxValue = BigInt(`0x${'f'.repeat(cleanId.length)}`);
}
// Use BigInt division first, then convert to number to maintain precision
return Number((value * BigInt(1_000_000)) / maxValue) / 1_000_000;
}
By adjusting the minimum and maximum for the UUIDv4 case, it ensures we use the entire range of 0-1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I verified the max, and it's also skewed for the same reason as the minimum, but only if you increase precision to 10^15. Not bad! Close enough that we won't notice, it's well under a percentage.
The suggestion I left here would give us a perfect distribution (JS rounding aside; it could get slightly better with bignumber.js and/or more precision). But what you have here is good enough I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion ! Converting uuid to hex value and then normalizing the UUID range to start from 0 instead of removing the bits within is a much better solution indeed! Adapted in 8724cc3
Uh oh!
There was an error while loading. Please reload this page.