| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <math.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <zircon/compiler.h> |
| |
| #include <iterator> |
| |
| #include <hid-parser/units.h> |
| |
| namespace { |
| |
| constexpr uint32_t system_mask = 0xF; |
| |
| // Taken from the Hid document on units. Each unit occupies |
| // a half byte (Nibble) so the shifts increment by 4 each time. |
| constexpr int system_shift = 0; |
| constexpr int length_shift = 4; |
| constexpr int mass_shift = 8; |
| constexpr int time_shift = 12; |
| constexpr int temperature_shift = 16; |
| constexpr int current_shift = 20; |
| constexpr int luminous_shift = 24; |
| |
| constexpr int8_t SignExtendFromNibble(int8_t nibble) { |
| // Expression taken and simplified from: |
| // http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend |
| return static_cast<int8_t>(((nibble & 0x0F) ^ 0x08) - 0x08); |
| } |
| |
| // Set the exponent of a given unit. |
| // Since exp will be converted to a nibble its values must range from [-8, 7]. |
| constexpr void SetUnitTypeExp(uint32_t& type, int8_t exp, int unit_shift) { |
| type = type | ((exp & 0x0F) << unit_shift); |
| } |
| |
| // Get the exponent of a given unit. |
| // Exponents range from [-8, 7]. |
| constexpr int8_t GetUnitTypeExp(uint32_t type, int unit_shift) { |
| return SignExtendFromNibble(static_cast<int8_t>((type & (0xF << unit_shift)) >> unit_shift)); |
| } |
| |
| constexpr bool IsSiToEng(hid::unit::System system_in, hid::unit::System system_out) { |
| return ((system_in == hid::unit::System::si_linear) || |
| (system_in == hid::unit::System::si_rotation)) && |
| ((system_out == hid::unit::System::eng_linear) || |
| (system_out == hid::unit::System::eng_rotation)); |
| } |
| |
| constexpr bool IsEngToSi(hid::unit::System system_in, hid::unit::System system_out) { |
| return ((system_in == hid::unit::System::eng_linear) || |
| (system_in == hid::unit::System::eng_rotation)) && |
| ((system_out == hid::unit::System::si_linear) || |
| (system_out == hid::unit::System::si_rotation)); |
| } |
| |
| bool ConvertDistance(double val_in, const hid::Unit& unit_in, double& val_out, |
| const hid::Unit& unit_out) { |
| int exp = hid::unit::GetLengthExp(unit_in); |
| auto system_in = hid::unit::GetSystem(unit_in); |
| auto system_out = hid::unit::GetSystem(unit_out); |
| |
| if ((exp == 0) || (system_in == system_out)) { |
| val_out = val_in; |
| return true; |
| } |
| |
| // Centimeters to Inches. |
| if ((system_in == hid::unit::System::si_linear) && |
| (system_out == hid::unit::System::eng_linear)) { |
| val_out = val_in * pow(0.393701, exp); |
| return true; |
| } |
| |
| // Inches to Centimeters. |
| if ((system_in == hid::unit::System::eng_linear) && |
| (system_out == hid::unit::System::si_linear)) { |
| val_out = val_in * pow(2.54, exp); |
| return true; |
| } |
| |
| // Degrees to Radians. |
| if ((system_in == hid::unit::System::eng_rotation) && |
| (system_out == hid::unit::System::si_rotation)) { |
| val_out = val_in * pow((3.14159 / 180.0), exp); |
| return true; |
| } |
| |
| // Radians to Degrees. |
| if ((system_in == hid::unit::System::si_rotation) && |
| (system_out == hid::unit::System::eng_rotation)) { |
| val_out = val_in * pow((180.0 / 3.14159), exp); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ConvertMass(double val_in, const hid::Unit& unit_in, double& val_out, |
| const hid::Unit& unit_out) { |
| int exp = hid::unit::GetMassExp(unit_in); |
| auto system_in = hid::unit::GetSystem(unit_in); |
| auto system_out = hid::unit::GetSystem(unit_out); |
| |
| if ((exp == 0) || (system_in == system_out)) { |
| val_out = val_in; |
| return true; |
| } |
| |
| // Grams to Slugs. |
| if (IsSiToEng(system_in, system_out)) { |
| val_out = val_in * pow(6.85218e-5, exp); |
| return true; |
| } |
| |
| // Slugs to Grams. |
| if (IsEngToSi(system_in, system_out)) { |
| val_out = val_in * pow(14593.9, exp); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool ConvertTemperature(double val_in, const hid::Unit& unit_in, double& val_out, |
| const hid::Unit& unit_out) { |
| int exp = hid::unit::GetTemperatureExp(unit_in); |
| auto system_in = hid::unit::GetSystem(unit_in); |
| auto system_out = hid::unit::GetSystem(unit_out); |
| |
| if ((exp == 0) || (system_in == system_out)) { |
| val_out = val_in; |
| return true; |
| } |
| |
| // Kelvin to Fahrenheit. |
| if (IsSiToEng(system_in, system_out)) { |
| val_out = (val_in - 273.15) * (9.0 / 5.0) + 32; |
| return true; |
| } |
| |
| // Fahrenheit to Kelvin. |
| if (IsEngToSi(system_in, system_out)) { |
| val_out = (val_in - 32) * (5.0 / 9.0) + 273.15; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace hid { |
| namespace unit { |
| |
| static constexpr bool CanConvertUnits(const hid::Unit& unit_in, const hid::Unit& unit_out) { |
| // If either units have length, then we have to check that the systems are compatible. |
| if ((unit_in.type & (0xF << length_shift)) || (unit_out.type & (0xF << length_shift))) { |
| // If the systems match, we can just check if the whole types match. |
| if (GetSystem(unit_in) == GetSystem(unit_out)) { |
| return unit_in.type == unit_out.type; |
| } |
| // If the systems don't match, then they have to either both be linear or both be rotation. |
| if (((GetSystem(unit_in) == System::si_linear) && |
| (GetSystem(unit_out) == System::eng_linear)) || |
| ((GetSystem(unit_in) == System::eng_linear) && |
| (GetSystem(unit_out) == System::si_linear)) || |
| ((GetSystem(unit_in) == System::si_rotation) && |
| (GetSystem(unit_out) == System::eng_rotation)) || |
| ((GetSystem(unit_in) == System::eng_rotation) && |
| (GetSystem(unit_out) == System::si_rotation))) { |
| // The types (excluding the system) have to match as well. |
| return (unit_in.type & ~system_mask) == (unit_out.type & ~system_mask); |
| } |
| // If we make it here, then there are incompatible systems. |
| return false; |
| } |
| // If there's no length, then all we have to check is the types without the systems. |
| return (unit_in.type & ~system_mask) == (unit_out.type & ~system_mask); |
| } |
| |
| void SetSystem(Unit& unit, hid::unit::System system) { |
| SetUnitTypeExp(unit.type, static_cast<int8_t>(system), system_shift); |
| } |
| |
| hid::unit::System GetSystem(const Unit& unit) { |
| int sys = GetUnitTypeExp(unit.type, system_shift); |
| switch (sys) { |
| case 1: |
| return hid::unit::System::si_linear; |
| case 2: |
| return hid::unit::System::si_rotation; |
| case 3: |
| return hid::unit::System::eng_linear; |
| case 4: |
| return hid::unit::System::eng_rotation; |
| default: |
| return hid::unit::System::reserved; |
| } |
| } |
| |
| void SetLengthExp(Unit& unit, int8_t exp) { SetUnitTypeExp(unit.type, exp, length_shift); } |
| |
| void SetMassExp(Unit& unit, int8_t exp) { SetUnitTypeExp(unit.type, exp, mass_shift); } |
| |
| void SetTimeExp(Unit& unit, int8_t exp) { SetUnitTypeExp(unit.type, exp, time_shift); } |
| |
| void SetTemperatureExp(Unit& unit, int8_t exp) { |
| SetUnitTypeExp(unit.type, exp, temperature_shift); |
| } |
| |
| void SetCurrentExp(Unit& unit, int8_t exp) { SetUnitTypeExp(unit.type, exp, current_shift); } |
| |
| void SetLuminousExp(Unit& unit, int8_t exp) { SetUnitTypeExp(unit.type, exp, luminous_shift); } |
| |
| int GetLengthExp(const Unit& unit) { return GetUnitTypeExp(unit.type, length_shift); } |
| |
| int GetMassExp(const Unit& unit) { return GetUnitTypeExp(unit.type, mass_shift); } |
| |
| int GetTimeExp(const Unit& unit) { return GetUnitTypeExp(unit.type, time_shift); } |
| |
| int GetTemperatureExp(const Unit& unit) { return GetUnitTypeExp(unit.type, temperature_shift); } |
| |
| int GetCurrentExp(const Unit& unit) { return GetUnitTypeExp(unit.type, current_shift); } |
| |
| int GetLuminousExp(const Unit& unit) { return GetUnitTypeExp(unit.type, luminous_shift); } |
| |
| bool ConvertUnits(const Unit& unit_in, double val_in, const Unit& unit_out, double* val_out) { |
| // If the units don't have the same measurements it's impossible to do a conversion. |
| if (!CanConvertUnits(unit_in, unit_out)) { |
| return false; |
| } |
| |
| double val = val_in * pow(10.0, unit_in.exp - unit_out.exp); |
| |
| if (unit_in.type == unit_out.type) { |
| *val_out = val; |
| return true; |
| } |
| |
| if (!ConvertDistance(val, unit_in, val, unit_out)) { |
| return false; |
| } |
| |
| if (!ConvertMass(val, unit_in, val, unit_out)) { |
| return false; |
| } |
| |
| if (!ConvertTemperature(val, unit_in, val, unit_out)) { |
| return false; |
| } |
| |
| *val_out = val; |
| return true; |
| } |
| |
| struct UnitConversion { |
| Unit unit; |
| UnitType unit_type; |
| }; |
| |
| static UnitConversion defined_units[] = { |
| { |
| .unit = |
| { |
| .type = |
| (static_cast<uint8_t>(System::si_linear) << system_shift) | (1 << length_shift), |
| .exp = -4, |
| }, |
| .unit_type = UnitType::Distance, |
| }, |
| |
| { |
| .unit = |
| { |
| .type = |
| (static_cast<uint8_t>(System::si_linear) << system_shift) | (1 << mass_shift), |
| .exp = -3, |
| }, |
| .unit_type = UnitType::Weight, |
| }, |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::eng_rotation) << system_shift) | |
| (1 << length_shift), |
| // This exponent is affected by length being 10^-2 m (cm). |
| .exp = -4, |
| }, |
| .unit_type = UnitType::Rotation, |
| }, |
| |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_rotation) << system_shift) | |
| (1 << length_shift) | (static_cast<uint8_t>(-1 & 0xF) << time_shift), |
| .exp = -3, |
| }, |
| .unit_type = UnitType::AngularVelocity, |
| }, |
| |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_linear) << system_shift) | |
| (1 << length_shift) | (static_cast<uint8_t>(-1 & 0xF) << time_shift), |
| // This exponent is affected by length being 10^-2 m (cm). |
| .exp = -1, |
| }, |
| .unit_type = UnitType::LinearVelocity, |
| }, |
| |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_linear) << system_shift) | |
| (1 << length_shift) | (static_cast<uint8_t>(-2 & 0xF) << time_shift), |
| // This exponent is affected by length being 10^-2 m (cm). |
| .exp = -1, |
| }, |
| .unit_type = UnitType::Acceleration, |
| }, |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_linear) << system_shift) | |
| (1 << mass_shift) | (static_cast<uint8_t>(-1 & 0xF) << current_shift) | |
| (static_cast<uint8_t>(-2 & 0xF) << time_shift), |
| .exp = 1, |
| }, |
| .unit_type = UnitType::MagneticFlux, |
| }, |
| |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_linear) << system_shift) | |
| (1 << luminous_shift), |
| .exp = 0, |
| }, |
| .unit_type = UnitType::Light, |
| }, |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_linear) << system_shift) | |
| (1 << luminous_shift) | (static_cast<uint8_t>(-2 & 0xF) << length_shift), |
| // This exponent is affected by length being 10^-2 m (cm). |
| .exp = -2, |
| }, |
| .unit_type = UnitType::Lux, |
| }, |
| |
| { |
| .unit = |
| { |
| .type = (static_cast<uint8_t>(System::si_linear) << system_shift) | |
| (1 << mass_shift) | (1 << length_shift) | |
| (static_cast<uint8_t>(-2 & 0xF) << time_shift), |
| // This exponent is affected by length being 10^-2 m (cm). |
| .exp = -2, |
| }, |
| .unit_type = UnitType::Pressure, |
| }, |
| }; |
| |
| Unit GetUnitFromUnitType(UnitType type) { |
| for (size_t i = 0; i < std::size(defined_units); i++) { |
| if (defined_units[i].unit_type == type) { |
| return defined_units[i].unit; |
| } |
| } |
| Unit other = {}; |
| return other; |
| } |
| |
| UnitType GetUnitTypeFromUnit(const Unit& unit) { |
| for (size_t i = 0; i < std::size(defined_units); i++) { |
| if (CanConvertUnits(defined_units[i].unit, unit)) { |
| return defined_units[i].unit_type; |
| } |
| } |
| if (unit.type == 0) { |
| return UnitType::None; |
| } |
| return UnitType::Other; |
| } |
| |
| double ConvertValToUnitType(const Unit& unit_in, double val_in) { |
| double val_out; |
| |
| for (size_t i = 0; i < std::size(defined_units); i++) { |
| if (ConvertUnits(unit_in, val_in, defined_units[i].unit, &val_out)) { |
| return val_out; |
| } |
| } |
| |
| // If we didn't find a matching UnitType than |unit_in| matches Other, which |
| // means we return the value unchanged. |
| return val_in; |
| } |
| |
| } // namespace unit |
| } // namespace hid |