在C中使用float在H.MMSS和十进制小时之间进行转换

ryhaxcpt  于 2023-06-21  发布在  其他
关注(0)|答案(2)|浏览(117)

我正在编写几个函数,以便在十进制小时(2小时30分钟是2.5小时)和H.MMSS格式(其中两个半小时是2.3000)之间进行转换。我最初使用double实现了这些函数,一切工作正常。然而,随着我的嵌入式项目规模的增长,我用完了闪存,不得不转移到浮动。
这里是我的MWE显示的问题

#include <stdio.h>
#include <math.h>

float toHMS(float inputTime) {
    float hours = 0;
    float minutes = 0;
    float seconds = 0;
    // get integral part for the hours
    hours = (int)inputTime;
    // transform into decimal minutes
    inputTime -= hours;
    inputTime *= 60;
    // Get the minutes
    minutes = (int)inputTime;
    // Get the seconds
    inputTime -= minutes;
    seconds = (int)(100 * inputTime);
    return hours + minutes/100 + seconds/ 10000;
} 

float toHours (float inputTime) {
    float hours = 0;
    float minutes = 0;
    float seconds = 0;
    // get integral part for the hours
    hours = (int)inputTime;
    inputTime = 100 * (inputTime - hours);
    minutes = (int)inputTime;
    inputTime = inputTime - minutes;
    seconds = 100 * inputTime;
    // normalize to 60
    minutes = minutes / 60;
    seconds = seconds / 3600;
    return hours + minutes + seconds;
}

float toHours2 (float inputTime) {
    float hours = 0;
    float minutes = 0;
    float seconds = 0;
    // get integral part for the hours
    hours = (int)inputTime;
    inputTime = roundf(100 * (inputTime - hours));
    minutes = (int)inputTime;
    inputTime = inputTime - minutes;
    seconds = 100 * inputTime;
    // normalize to 60
    minutes = minutes / 60;
    seconds = seconds / 3600;
    return hours + minutes + seconds;
}

void main(void) {
    float hms = toHMS(2.5f);
    printf("toHMS(2.5f): %.9f\n", hms);
    printf("toHours(toHMS(2.5f)): %.9f\n", toHours(hms));
    printf("\n");
    printf("toHours(2.3f): %.9f\n", toHours(2.3f));
    printf("\n");
    printf("toHours2(2.3f): %.9f\n", toHours2(2.3f));
}

结果是:

toHMS(2.5f): 2.299999952
toHours(toHMS(2.5f)): 2.511111021

toHours(2.3f): 2.511111021

toHours2(2.3f): 2.500000000

在我看来,问题出在toHours()中,因为2.3在单个精度浮点数中是2.299999952。在toHours 2()中添加roundf()解决了这个问题。
这是唯一需要的修复,还是有其他输入数字会导致计算失败?我正在努力寻找一个解决方案,并向自己证明它适用于所有可能的输入数字,而不仅仅是2.3f。
谢谢!

jexiocij

jexiocij1#

toHours失败,因为2.30不能以float常用的格式表示,IEEE-754“单精度”(binary 64)。最接近的可表示值是2.2999999523162841796875。您的例程正确地返回近似的十进制时间2小时29分钟99.999523162841796875秒,约为2.5111。但那不是你想这样解释的2.299999523162841796875;你想把它解释为“2.3000”。下面更多关于如何做到这一点。
toHours2在使用roundf时失败。对于2.3050f的输入,它返回约2.516666651,持续2小时31分钟,但正确答案约为2.513888836。
对于合理的时间,您可以将toHours实现为:

float toHours(float inputTime)
{
    long t = lround(inputTime * 1e4);
    int seconds = t % 100;
    int minutes = t / 100 % 100;
    int hours   = t / 10000;
    return (seconds / 60. + minutes) / 60. + hours;
}

只要时间没有增长太大,并且来自一个整数秒的值,lround(inputTime * 1e4)就会有效地恢复数字,修复转换为二进制浮点时发生的舍入。然后,可以使用整数算术将该数字分开。对于超过1,024小时的时间,您可能会看到舍入问题,因为1,024是binary 32格式不能再表示间隔小于0.0001的数字的点(间距为1,024/223,约为0.000122),您需要它来区分“H.MMSS”格式的秒。

m3eecexj

m3eecexj2#

......或者是否有其他输入数字会使计算出错?
是的。这些是因为重复的圆化而出现的。考虑浮点值刚好低于整数的 * hours *。
相反,最小化舍入并使用整数数学执行大多数计算。
hours更改为 * H. MMSS *,缩放,舍入,分割为H,M,S。

//Untested code

float toHMS(float inputTime) {
    long long t = llroundf(inputTime * 3600.0f);  // Rounded result
    int sec = t % 60;
    t /= 60;
    int min = t % 60;
    t /= 60;
    t = t * 10000 + min*100 + sec;
    return t/10000.0f; // 2nd Rounding
}

类似的代码将H.MMSS转换为 * hours *。

float toHours(float inputTime) {
    long long t = llroundf(inputTime * 10000.0f);  // Rounded result
    int sec = t % 100;
    t /= 100;
    int min = t % 100;
    t /= 100;
    t = t * 3600 + min*60 + sec;
    return t/3600.0f; // 2nd Rounding
}

相关问题