Skip to content
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

Use ULP based comparison. #139

Merged
merged 4 commits into from
Nov 30, 2021

Conversation

BruceDai
Copy link
Collaborator

int64_t GetBitwise(float value) {
    int64_t bitwiseValue = (value < T(0)) ? ~int64_t(0) : 0; // Extend sign.
    *std::launder(reinterpret_cast<T*>(&bitwiseValue)) = value;
    return bitwiseValue;
}

bool CompareUlp(float a, float b, uint64_t ulp) {
    return static_cast<uint64_t>(abs(GetBitwise(a) - GetBitwise(b))) > ulp;
}

@huningxin I implemented JavaScript compareUlp() function to align Chai's comment on #2, PTAL, thanks.

test/utils.js Outdated
@@ -36,11 +36,28 @@ export function almostEqual(a, b, criteria) {
}
}

export function checkValue(
output, expected, criteria = opFp32AccuracyCriteria) {
export function compareUlp(a, b, nulp = 1, base = 64) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please split this function into getBitwise and cmopareUlp as Chai's C++ example.

For getBitwise, please change base to a string type, like float32 and float64. BTW, I don't think we support float64 in spec so far, do we?

test/utils.js Outdated
const bView = new DataView(bBuffer);

if (base === 64) {
aView.setFloat64(0, a, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we should not restrict to little endian.

test/utils.js Outdated
export function compareUlp(a, b, nulp = 1, base = 64) {
const aBuffer = new ArrayBuffer(8);
const bBuffer = new ArrayBuffer(8);
const aView = new DataView(aBuffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should populate the buffer with 0 or ~0, as C++ code does

int64_t bitwiseValue = (value < T(0)) ? ~int64_t(0) : 0; // Extend sign.

You probably can use a JavaScript BigInt object do that e.g.,

aView.setBigInt64(0, value < 0 ? ~BigInt(0) : BigInt(0))

test/utils.js Show resolved Hide resolved
test/utils.js Outdated
bView.setFloat32(0, b, true);
}

return Math.abs(new BigUint64Array(aBuffer) - new BigUint64Array(bBuffer)) >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For getBitwise, you could return aView.getBigInt64(0) here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Math.abs doesn't work with BigInt64, so you probably need to implement it by yourself. Fortunately, it would straightfoward, as BigInt64 support comparison and negate.

@BruceDai
Copy link
Collaborator Author

Thanks @huningxin .

I updated PR following your suggestion, now new getBitwise() function is only for float32 data

function getBitwise(value) {
  const buffer = new ArrayBuffer(4);
  const view = new DataView(buffer);
  view.setFloat32(0, value);
  return view.getInt32(0);
}

About bitwise of float32 data, I compared the return value of this JS function with one of native C++ GetBitwise() function, they are same.

@huningxin Please take a look again, thanks.

test/utils.js Outdated
export function checkValue(
output, expected, criteria = opFp32AccuracyCriteria) {
function getBitwise(value) {
const buffer = new ArrayBuffer(4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use length 8 to accommodate BigInt

test/utils.js Outdated
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setFloat32(0, value);
return view.getInt32(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use getBigInt64 and return a BigInt as I commented #139 (comment)

test/utils.js Outdated
output, expected, criteria = opFp32AccuracyCriteria) {
function getBitwise(value) {
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I commented before, you should populate the buffer #139 (comment)

test/utils.js Outdated
return view.getInt32(0);
}

export function compareUlp(a, b, nulp = 1, format = 'float32') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nulp should be BigInt

test/utils.js Outdated
}

export function compareUlp(a, b, nulp = 1, format = 'float32') {
assert(format === 'float32', `Format ${format} is not supported.`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format is not a accurate name, probably dataType

test/utils.js Outdated
@@ -36,11 +36,26 @@ export function almostEqual(a, b, criteria) {
}
}

export function checkValue(
output, expected, criteria = opFp32AccuracyCriteria) {
function getBitwise(value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is only for float32, please name it as getBitwiseFloat32

test/utils.js Outdated
const aBitwise = getBitwise(a);
const bBitwise = getBitwise(b);
let distance = aBitwise - bBitwise;
distance = distance > 0 ? distance : -distance;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= 0 ?

@BruceDai
Copy link
Collaborator Author

Thanks much @huningxin.
I've updated commit to fix your review comments, PTAL, Thanks.

Copy link
Contributor

@huningxin huningxin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add js doc for these two methods. Thanks.

test/utils.js Outdated
@@ -36,11 +36,28 @@ export function almostEqual(a, b, criteria) {
}
}

export function checkValue(
output, expected, criteria = opFp32AccuracyCriteria) {
export function getBitwiseFloat32(value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably makes sense to support other data types and make this function more generic, like getBitwise. It would also align with C++ implementation.

test/utils.js Outdated
const buffer = new ArrayBuffer(8);
const int64Array = new BigInt64Array(buffer);
int64Array[0] = value < 0 ? ~BigInt(0) : BigInt(0);
const f32Array = new Float32Array(buffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create the typed array based on dataType.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can assert the unsupported data types here.

test/utils.js Outdated
}

export function compareUlp(a, b, nulp = 1n, dataType = 'float32') {
assert(dataType === 'float32', `DataType ${dataType} is not supported.`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid assertion here.

@BruceDai
Copy link
Collaborator Author

@huningxin I updated PR, PTAL, thanks.

Copy link
Contributor

@huningxin huningxin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks @BruceDai .

test/utils.js Outdated
@@ -45,6 +45,9 @@ export function almostEqual(a, b, criteria) {
* @return {number} A 64-bit signed integer.
*/
export function getBitwise(value, dataType) {
if (Object.is(value, -0)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huningxin I caught an issue of -0, the returns of

getBitwise(-0, "float32")

is 2147483648n, so the distance between -0 and 0 is 2147483648n, it isn't the same as the distance of 0n between -0 and 0 by native C++ compareUlp() function.

I added this workaround to deal with -0. What's your opinion? Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try -0.0 in native C++?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @huningxin,
I tested with -0.0 in native C++, the GitBitwise() function returns 2147483648n, and the distance between -0.0 and 0 is 2147483648n.
I checked that return of Object.is(-0.0, -0) is ture, that's, -0.0 and -0 are same value in JavaScript.
Should we make the behave in JavaScript align with native behave? Any suggestion? Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the testing. I think in JavaScript -0 and -0.0 are same, because they are both Numbers. In C++, -0 is integer and -0.0 is float, so they are different.

With that, I think it doesn't need this conversion with float32 data type. For integer data type, it probably needs to use parseInt(v), it will convert -0 (float) to 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @huningxin.
I will raise this special case on coming WG meeting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Could you please file an issue to log this case? It could be used for WG discussion. Thanks.

@BruceDai
Copy link
Collaborator Author

I rebased this PR to only add JavaScript's ULP based comparison implementation.

@huningxin PTAL, thanks.

@huningxin huningxin merged commit e89a663 into webmachinelearning:master Nov 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants