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

feat(new tool) Floating Point Number Converter #1348

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ declare module '@vue/runtime-core' {
Encryption: typeof import('./src/tools/encryption/encryption.vue')['default']
EtaCalculator: typeof import('./src/tools/eta-calculator/eta-calculator.vue')['default']
FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default']
FloatingPointNumberConverter: typeof import('./src/tools/floating-point-number-converter/floating-point-number-converter.vue')['default']
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
Expand Down
4 changes: 4 additions & 0 deletions locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ tools:
description: >-
Konvertiere Zahlen zwischen verschiedenen Basen (Dezimal, Hexadezimal,
Binär, Oktal, Base64, ...).
floating-point-converter:
title: Konverter für Fließkommazahlen
description: >-
Konvertiere eine Dezimalzahl in eine IEEE-754 binäre Fließkommazahl oder umgekehrt.
yaml-to-json-converter:
title: YAML zu JSON
description: Konvertiere YAML einfach in JSON mit diesem Live-Online-Konverter.
Expand Down
4 changes: 4 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ tools:
title: Integer base converter
description: Convert a number between different bases (decimal, hexadecimal, binary, octal, base64, ...)

floating-point-converter:
title: Floating point number converter
description: Convert a decimal number to a IEEE 754 binary floating point number or vice versa.

yaml-to-json-converter:
title: YAML to JSON converter
description: Simply convert YAML to JSON with this online live converter.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, expect, it } from 'vitest';
import { convertBinaryToDecimal, convertDecimalToBinary } from './floating-point-number-converter.model';

describe('floating-point-number-converter', () => {
describe('convertDecimalToBinary', () => {
it('Should convert a decimal number to a floating point binary number (IEEE-754)', () => {
// 32-Bit
expect(convertDecimalToBinary({ value: '0', bitCount: 32 })).toEqual('00000000000000000000000000000000');
expect(convertDecimalToBinary({ value: '-0', bitCount: 32 })).toEqual('10000000000000000000000000000000');
expect(convertDecimalToBinary({ value: 'NaN', bitCount: 32 })).toEqual('01111111110000000000000000000000');
expect(convertDecimalToBinary({ value: 'Infinity', bitCount: 32 })).toEqual('01111111100000000000000000000000');
expect(convertDecimalToBinary({ value: '-Infinity', bitCount: 32 })).toEqual('11111111100000000000000000000000');
expect(convertDecimalToBinary({ value: '2.5', bitCount: 32 })).toEqual('01000000001000000000000000000000');
expect(convertDecimalToBinary({ value: '-128.25', bitCount: 32 })).toEqual('11000011000000000100000000000000');
expect(convertDecimalToBinary({ value: '0.1', bitCount: 32 })).toEqual('00111101110011001100110011001101');
expect(convertDecimalToBinary({ value: '3.4028235e38', bitCount: 32 })).toEqual('01111111011111111111111111111111');
expect(convertDecimalToBinary({ value: '1.1754942e-38', bitCount: 32 })).toEqual('00000000011111111111111111111111');

// 64-Bit
expect(convertDecimalToBinary({ value: '0', bitCount: 64 })).toEqual('0000000000000000000000000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: '-0', bitCount: 64 })).toEqual('1000000000000000000000000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: 'NaN', bitCount: 64 })).toEqual('0111111111111000000000000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: 'Infinity', bitCount: 64 })).toEqual('0111111111110000000000000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: '-Infinity', bitCount: 64 })).toEqual('1111111111110000000000000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: '2.5', bitCount: 64 })).toEqual('0100000000000100000000000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: '-128.25', bitCount: 64 })).toEqual('1100000001100000000010000000000000000000000000000000000000000000');
expect(convertDecimalToBinary({ value: '0.1', bitCount: 64 })).toEqual('0011111110111001100110011001100110011001100110011001100110011010');
expect(convertDecimalToBinary({ value: '1.7976931348623157e308', bitCount: 64 })).toEqual('0111111111101111111111111111111111111111111111111111111111111111');
expect(convertDecimalToBinary({ value: '2.225073858507201e-308', bitCount: 64 })).toEqual('0000000000001111111111111111111111111111111111111111111111111111');
});
});
describe('convertBinaryToDecimal', () => {
it('Should convert a floating point binary number (IEEE-754) to a decimal number', () => {
// 32-Bit
expect(convertBinaryToDecimal({ value: '00000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('0.00000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '10000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('-0.00000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '01111111110000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('NaN');
expect(convertBinaryToDecimal({ value: '11111111110000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('NaN');
expect(convertBinaryToDecimal({ value: '01111111110000000000000000000001', decimalPrecision: '32', removeZeroPadding: false })).toEqual('NaN');
expect(convertBinaryToDecimal({ value: '01111111100000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('Infinity');
expect(convertBinaryToDecimal({ value: '11111111100000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('-Infinity');
expect(convertBinaryToDecimal({ value: '01000000001000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('2.50000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '01000000001000000000000000000000', decimalPrecision: '32', removeZeroPadding: true })).toEqual('2.5');
expect(convertBinaryToDecimal({ value: '11000011000000000100000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('-128.25000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '00111101110011001100110011001101', decimalPrecision: '32', removeZeroPadding: false })).toEqual('0.10000000149011611938476562500000');
expect(convertBinaryToDecimal({ value: '00111101110011001100110011001101', decimalPrecision: '10', removeZeroPadding: false })).toEqual('0.1000000015');
expect(convertBinaryToDecimal({ value: '00111101110011001100110011001101', decimalPrecision: '1', removeZeroPadding: false })).toEqual('0.1');
expect(convertBinaryToDecimal({ value: '01111111011111111111111111111111', decimalPrecision: '32', removeZeroPadding: false })).matches(/^3\.402823\d*e\+?38$/);
expect(convertBinaryToDecimal({ value: '00000000011111111111111111111111', decimalPrecision: '32', removeZeroPadding: false })).toEqual('0.00000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '00000000011111111111111111111111', decimalPrecision: '64', removeZeroPadding: false })).toEqual('0.0000000000000000000000000000000000000117549421069244107548702944');
expect(convertBinaryToDecimal({ value: '00000000011111111111111111111111', decimalPrecision: '', removeZeroPadding: false })).matches(/^1\.1754942\d*e-38$/);

// 64-Bit
expect(convertBinaryToDecimal({ value: '0000000000000000000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('0.00000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '1000000000000000000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('-0.00000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '0111111111111000000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('NaN');
expect(convertBinaryToDecimal({ value: '1111111111111000000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('NaN');
expect(convertBinaryToDecimal({ value: '0111111111111000000000000000000000000000000000000000000000000001', decimalPrecision: '32', removeZeroPadding: false })).toEqual('NaN');
expect(convertBinaryToDecimal({ value: '0111111111110000000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('Infinity');
expect(convertBinaryToDecimal({ value: '1111111111110000000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('-Infinity');
expect(convertBinaryToDecimal({ value: '0100000000000100000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('2.50000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '0100000000000100000000000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: true })).toEqual('2.5');
expect(convertBinaryToDecimal({ value: '1100000001100000000010000000000000000000000000000000000000000000', decimalPrecision: '32', removeZeroPadding: false })).toEqual('-128.25000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '0011111110111001100110011001100110011001100110011001100110011010', decimalPrecision: '32', removeZeroPadding: false })).toEqual('0.10000000000000000555111512312578');
expect(convertBinaryToDecimal({ value: '0011111110111001100110011001100110011001100110011001100110011010', decimalPrecision: '10', removeZeroPadding: false })).toEqual('0.1000000000');
expect(convertBinaryToDecimal({ value: '0011111110111001100110011001100110011001100110011001100110011010', decimalPrecision: '1', removeZeroPadding: false })).toEqual('0.1');
expect(convertBinaryToDecimal({ value: '0111111111101111111111111111111111111111111111111111111111111111', decimalPrecision: '32', removeZeroPadding: false })).matches(/^1\.79769313\d*e\+?308$/);
expect(convertBinaryToDecimal({ value: '0000000000001111111111111111111111111111111111111111111111111111', decimalPrecision: '32', removeZeroPadding: false })).toEqual('0.00000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '0000000000001111111111111111111111111111111111111111111111111111', decimalPrecision: '100', removeZeroPadding: false })).toEqual('0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000');
expect(convertBinaryToDecimal({ value: '0000000000001111111111111111111111111111111111111111111111111111', decimalPrecision: '', removeZeroPadding: false })).matches(/^2\.2250738585\d*e-308$/);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export function convertDecimalToBinary({ value, bitCount }: { value: string; bitCount: number }) {
let number = Number.parseFloat(value.replace(/\s/g, ''));
if (value.match(/^-?inf(inity)?$/i)) {
// const sign = value.startsWith('-') ? 1 : 0;
// return `${sign}${'1'.repeat(exponentBits)}${'0'.repeat(mantissaBits)}`;
number = (value.startsWith('-') ? -2 : 2) / 0;
}

switch (bitCount) {
case 32: { // Single precision
const uint = new Uint32Array(Float32Array.of(number).buffer);
return uint[0].toString(2).padStart(32, '0');
}
case 64: { // Double precision
const uint = new Uint32Array(Float64Array.of(number).buffer);
return [...uint].slice(0, 2).reverse().map(p => p.toString(2).padStart(32, '0')).join('');
}
default:
throw new Error('Unsupported bit count. Only 32 and 64 are allowed.');
}
}

export function convertBinaryToDecimal({ value, decimalPrecision, removeZeroPadding }: { value: string; decimalPrecision: string; removeZeroPadding: boolean }) {
if (value.match(/[^01]/)) {
throw new Error('Not a binary number.');
}
if (decimalPrecision.match(/[^\d]/)) {
throw new Error('Decimal Precision must be a positive whole number.');
}

let result: number;
switch (value.length) {
case 32: {
const binary = [Number.parseInt(value, 2)];
result = (new Float32Array(Uint32Array.from(binary).buffer))[0];
break;
}
case 64: {
const binary = [Number.parseInt(value.substring(32), 2), Number.parseInt(value.substring(0, 32), 2)];
result = (new Float64Array(Uint32Array.from(binary).buffer))[0];
break;
}
default:
throw new Error('Invalid length. Supply a binary string with length 32 or 64.');
}

const zeroNegative = result === 0 && 2 / result === Number.NEGATIVE_INFINITY;
let resultString = decimalPrecision.length === 0 ? result.toString() : result.toFixed(Number.parseInt(decimalPrecision));
if (removeZeroPadding) {
resultString = resultString.replace(/\.(\d+?)(0+)$/, '.$1');
}
return (zeroNegative ? '-' : '') + resultString;
}

export function calcErrorDueToConversion({ decimalInput, actualValue }: { decimalInput: string; actualValue: string }) {
return (Number.parseFloat(decimalInput) - Number.parseFloat(actualValue)).toFixed(32).replace(/\.(\d+?)(0+)$/, '.$1');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<script setup lang="ts">
import InputCopyable from '../../components/InputCopyable.vue';
import { convertBase } from '../integer-base-converter/integer-base-converter.model';
import { calcErrorDueToConversion, convertBinaryToDecimal, convertDecimalToBinary } from './floating-point-number-converter.model';
import { getErrorMessageIfThrows } from '@/utils/error';

const bitCount = ref(32);
const decimalInput = ref('42.42');
const binaryOutput = ref();
const actualValue = ref();

const binaryInput = ref('01000010001010011010111000010100');
const decimalPrecision = ref('32');
const showTrailingZeros = ref(false);

function errorlessBinaryToDecimalConversion(...args: Parameters<typeof convertBinaryToDecimal>) {
try {
return convertBinaryToDecimal(...args);
}
catch (err) {
return '';
}
}

const binaryToDecimalError = computed(() =>
getErrorMessageIfThrows(() =>
convertBinaryToDecimal({ value: binaryInput.value, decimalPrecision: decimalPrecision.value, removeZeroPadding: false }),
),
);
</script>

<template>
<c-card title="Decimal to Binary" style="min-width: 650px">
<c-input-text
v-model:value="decimalInput"
label="Decimal Number"
placeholder="Put your decimal number here (ex: 42.42)"
label-position="left"
label-width="210px"
label-align="right"
mb-2
/>

<c-select
v-model:value="bitCount"
mb-4
label="Bit Count"
label-position="left"
label-width="210px"
label-align="right"
:options="[
{
label: '32-Bit (Single precision)',
value: 32,
},
{
label: '64-Bit (Double precision)',
value: 64,
},
]"
/>

<n-divider />

<InputCopyable
label="Binary Number"
placeholder="Binary Number"
:value="binaryOutput = convertDecimalToBinary({ value: decimalInput, bitCount })"
readonly
label-position="left"
label-width="210px"
label-align="right"
mb-2
/>

<InputCopyable
label="Hexadecimal Representation"
placeholder="Hexadecimal Representation"
:value="convertBase({ value: binaryOutput, fromBase: 2, toBase: 16 })"
readonly
label-position="left"
label-width="210px"
label-align="right"
mb-2
/>

<InputCopyable
label="Actually stored value"
placeholder="Actually stored value"
:value="actualValue = errorlessBinaryToDecimalConversion({ value: binaryOutput, decimalPrecision: '32', removeZeroPadding: true })"
readonly
label-position="left"
label-width="210px"
label-align="right"
mb-2
/>

<InputCopyable
label="Error due to conversion"
placeholder="Error due to conversion"
:value="calcErrorDueToConversion({ decimalInput, actualValue })"
readonly
label-position="left"
label-width="210px"
label-align="right"
mb-2
/>
</c-card>

<c-card title="Binary to Decimal" style="min-width: 650px">
<c-input-text
v-model:value="binaryInput"
label="Binary Number"
placeholder="Put your binary number here (ex: 01000010001010011010111000010100)"
label-position="left"
label-width="140px"
label-align="right"
mb-2
/>

<c-input-text
v-model:value="decimalPrecision"
label="Decimal Precision"
placeholder="Choose the decimal precision (digits after the decimal point)."
label-position="left"
label-width="140px"
label-align="right"
mb-2
/>

<n-form-item
label="Show Trailing Zeros"
label-placement="left"
label-width="140px"
label-align="right"
>
<n-switch
v-model:value="showTrailingZeros"
/>
</n-form-item>

<n-alert v-if="binaryToDecimalError" style="margin-top: 25px" type="error">
{{ binaryToDecimalError }}
</n-alert>

<n-divider />

<InputCopyable
label="Decimal Number"
placeholder="Decimal Number"
:value="errorlessBinaryToDecimalConversion({ value: binaryInput, decimalPrecision, removeZeroPadding: !showTrailingZeros })"
readonly
label-position="left"
label-width="140px"
label-align="right"
mb-2
/>
</c-card>
</template>
13 changes: 13 additions & 0 deletions src/tools/floating-point-number-converter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ArrowsLeftRight } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';

export const tool = defineTool({
name: translate('tools.floating-point-converter.title'),
path: '/floating-point-converter',
description: translate('tools.floating-point-converter.description'),
keywords: ['converter', 'floating', 'point', 'number', 'converter', 'binary', 'decimal'],
component: () => import('./floating-point-number-converter.vue'),
icon: ArrowsLeftRight,
createdAt: new Date('2024-10-12'),
});
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as floatingPointNumberConverter } from './floating-point-number-converter';
import { tool as emailNormalizer } from './email-normalizer';

import { tool as asciiTextDrawer } from './ascii-text-drawer';
Expand Down Expand Up @@ -98,6 +99,7 @@ export const toolsByCategory: ToolCategory[] = [
components: [
dateTimeConverter,
baseConverter,
floatingPointNumberConverter,
romanNumeralConverter,
base64StringConverter,
base64FileConverter,
Expand Down
Loading